diff --git a/src/VirtualClient/VirtualClient.Actions.UnitTests/Examples/Sysbench/SysbenchExample1.txt b/src/VirtualClient/VirtualClient.Actions.UnitTests/Examples/Sysbench/SysbenchExample1.txt new file mode 100644 index 0000000000..0ee7860991 --- /dev/null +++ b/src/VirtualClient/VirtualClient.Actions.UnitTests/Examples/Sysbench/SysbenchExample1.txt @@ -0,0 +1,38 @@ +sysbench 1.1.0 (using bundled LuaJIT 2.1.0-beta3) + +Running the test with following options: +Number of threads: 8 +Initializing random number generator from current time + + +Initializing worker threads... + +Threads started! + +SQL statistics: + queries performed: + read: 9720106 + write: 0 + other: 0 + total: 9720106 + transactions: 9720106 (32400.24 per sec.) + queries: 9720106 (32400.24 per sec.) + ignored errors: 0 (0.00 per sec.) + reconnects: 0 (0.00 per sec.) + +Throughput: + events/s (eps): 32400.2399 + time elapsed: 300.0011s + total number of events: 9720106 + +Latency (ms): + min: 0.16 + avg: 0.25 + max: 13.68 + 95th percentile: 0.28 + sum: 2389444.97 + +Threads fairness: + events (avg/stddev): 1215013.2500/22596.17 + execution time (avg/stddev): 298.6806/0.05 + diff --git a/src/VirtualClient/VirtualClient.Actions.UnitTests/Sysbench/SysbenchClientExecutorTests.cs b/src/VirtualClient/VirtualClient.Actions.UnitTests/Sysbench/SysbenchClientExecutorTests.cs index 72273bf438..c697539ce1 100644 --- a/src/VirtualClient/VirtualClient.Actions.UnitTests/Sysbench/SysbenchClientExecutorTests.cs +++ b/src/VirtualClient/VirtualClient.Actions.UnitTests/Sysbench/SysbenchClientExecutorTests.cs @@ -100,8 +100,6 @@ public async Task SysbenchClientExecutorRunsTheExpectedWorkloadCommand() commandExecuted = true; } - Assert.IsTrue(commandExecuted); - InMemoryProcess process = new InMemoryProcess { StartInfo = new ProcessStartInfo @@ -124,6 +122,8 @@ public async Task SysbenchClientExecutorRunsTheExpectedWorkloadCommand() { await SysbenchExecutor.ExecuteAsync(CancellationToken.None); } + + Assert.IsTrue(commandExecuted); } [Test] @@ -146,8 +146,6 @@ public async Task SysbenchClientExecutorUsesDefinedParametersWhenRunningTheWorkl commandExecuted = true; } - Assert.IsTrue(commandExecuted); - InMemoryProcess process = new InMemoryProcess { StartInfo = new ProcessStartInfo @@ -170,6 +168,8 @@ public async Task SysbenchClientExecutorUsesDefinedParametersWhenRunningTheWorkl { await SysbenchExecutor.ExecuteAsync(CancellationToken.None); } + + Assert.IsTrue(commandExecuted); } [Test] @@ -189,8 +189,6 @@ public async Task SysbenchClientExecutorRunsTheExpectedBalancedScenario() commandExecuted = true; } - Assert.IsTrue(commandExecuted); - InMemoryProcess process = new InMemoryProcess { StartInfo = new ProcessStartInfo @@ -213,6 +211,8 @@ public async Task SysbenchClientExecutorRunsTheExpectedBalancedScenario() { await SysbenchExecutor.ExecuteAsync(CancellationToken.None); } + + Assert.IsTrue(commandExecuted); } [Test] @@ -232,8 +232,6 @@ public async Task SysbenchClientExecutorRunsInMemoryScenario() commandExecuted = true; } - Assert.IsTrue(commandExecuted); - InMemoryProcess process = new InMemoryProcess { StartInfo = new ProcessStartInfo @@ -256,6 +254,8 @@ public async Task SysbenchClientExecutorRunsInMemoryScenario() { await SysbenchExecutor.ExecuteAsync(CancellationToken.None).ConfigureAwait(false); } + + Assert.IsTrue(commandExecuted); } [Test] @@ -275,8 +275,6 @@ public async Task SysbenchClientExecutorRunsTheExpectedTPCCWorkloadCommand() commandExecuted = true; } - Assert.IsTrue(commandExecuted); - InMemoryProcess process = new InMemoryProcess { StartInfo = new ProcessStartInfo @@ -299,6 +297,8 @@ public async Task SysbenchClientExecutorRunsTheExpectedTPCCWorkloadCommand() { await SysbenchExecutor.ExecuteAsync(CancellationToken.None); } + + Assert.IsTrue(commandExecuted); } [Test] diff --git a/src/VirtualClient/VirtualClient.Actions.UnitTests/Sysbench/SysbenchConfigurationTests.cs b/src/VirtualClient/VirtualClient.Actions.UnitTests/Sysbench/SysbenchConfigurationTests.cs index 98268dbf24..4bf12bbe70 100644 --- a/src/VirtualClient/VirtualClient.Actions.UnitTests/Sysbench/SysbenchConfigurationTests.cs +++ b/src/VirtualClient/VirtualClient.Actions.UnitTests/Sysbench/SysbenchConfigurationTests.cs @@ -77,7 +77,7 @@ public async Task SysbenchConfigurationSkipsSysbenchInitialization() string[] expectedCommands = { - $"python3 {this.mockPackagePath}/populate-database.py --dbName sbtest --databaseSystem MySQL --benchmark OLTP --tableCount 10 --recordCount 1000 --threadCount 8 --password [A-Za-z0-9+/=]+", + $"python3 {this.mockPackagePath}/populate-database.py --dbName sbtest --databaseSystem MySQL --benchmark OLTP --threadCount 8 --tableCount 10 --recordCount 1000 --password [A-Za-z0-9+/=]+", }; int commandNumber = 0; @@ -122,7 +122,7 @@ public async Task SysbenchConfigurationPreparesDatabase() string[] expectedCommands = { $"python3 {this.mockPackagePath}/configure-workload-generator.py --distro Ubuntu --databaseSystem MySQL --packagePath {this.mockPackagePath}", - $"python3 {this.mockPackagePath}/populate-database.py --dbName sbtest --databaseSystem MySQL --benchmark OLTP --tableCount 10 --recordCount 1000 --threadCount 8 --password [A-Za-z0-9+/=]+", + $"python3 {this.mockPackagePath}/populate-database.py --dbName sbtest --databaseSystem MySQL --benchmark OLTP --threadCount 8 --tableCount 10 --recordCount 1000 --password [A-Za-z0-9+/=]+", }; int commandNumber = 0; @@ -173,7 +173,7 @@ public async Task SysbenchConfigurationUsesDefinedParametersWhenRunningTheWorklo string[] expectedCommands = { $"python3 {this.mockPackagePath}/configure-workload-generator.py --distro Ubuntu --databaseSystem MySQL --packagePath {this.mockPackagePath}", - $"python3 {this.mockPackagePath}/populate-database.py --dbName sbtest --databaseSystem MySQL --benchmark OLTP --tableCount 40 --recordCount 1000 --threadCount 16 --password [A-Za-z0-9+/=]+", + $"python3 {this.mockPackagePath}/populate-database.py --dbName sbtest --databaseSystem MySQL --benchmark OLTP --threadCount 16 --tableCount 40 --recordCount 1000 --password [A-Za-z0-9+/=]+", }; int commandNumber = 0; @@ -386,7 +386,7 @@ public async Task SysbenchConfigurationProperlyExecutesPostgreSQLOLTPConfigurabl string[] expectedCommands = { - $"python3 {this.mockPackagePath}/populate-database.py --dbName sbtest --databaseSystem PostgreSQL --benchmark OLTP --tableCount 40 --recordCount 1000 --threadCount 16 --password [A-Za-z0-9+/=]+" + $"python3 {this.mockPackagePath}/populate-database.py --dbName sbtest --databaseSystem PostgreSQL --benchmark OLTP --threadCount 16 --tableCount 40 --recordCount 1000 --password [A-Za-z0-9+/=]+" }; int commandNumber = 0; diff --git a/src/VirtualClient/VirtualClient.Actions.UnitTests/Sysbench/SysbenchMetricsParserTests.cs b/src/VirtualClient/VirtualClient.Actions.UnitTests/Sysbench/SysbenchMetricsParserTests.cs index 170837e6aa..2dbd5d8d66 100644 --- a/src/VirtualClient/VirtualClient.Actions.UnitTests/Sysbench/SysbenchMetricsParserTests.cs +++ b/src/VirtualClient/VirtualClient.Actions.UnitTests/Sysbench/SysbenchMetricsParserTests.cs @@ -46,5 +46,17 @@ public void SysbenchParserParsesCorrectly() MetricAssert.Exists(metrics, "latency p95", 68.05, "milliseconds"); MetricAssert.Exists(metrics, "latency sum", 7458385.25, "milliseconds"); } + + [Test] + public void SysbenchParserParsesMetricsMetadataCorrectly() + { + string rawText = File.ReadAllText(Path.Combine(examplesDirectory, "SysbenchExample1.txt")); + SysbenchMetricsParser parser = new SysbenchMetricsParser(rawText); + + IList metrics = parser.Parse(); + Assert.AreEqual(17, metrics.Count); + Assert.IsTrue(metrics[0].Metadata.ContainsKey("sysbench_version")); + Assert.IsTrue(metrics[0].Metadata["sysbench_version"].Equals("1.1.0")); + } } } \ No newline at end of file diff --git a/src/VirtualClient/VirtualClient.Actions/Sysbench/SysbenchClientExecutor.cs b/src/VirtualClient/VirtualClient.Actions/Sysbench/SysbenchClientExecutor.cs index 9c891a8df0..321793bfda 100644 --- a/src/VirtualClient/VirtualClient.Actions/Sysbench/SysbenchClientExecutor.cs +++ b/src/VirtualClient/VirtualClient.Actions/Sysbench/SysbenchClientExecutor.cs @@ -6,7 +6,7 @@ namespace VirtualClient.Actions using System; using System.Collections.Generic; using System.Linq; - using System.Net; + using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; @@ -137,9 +137,23 @@ private void CaptureMetrics(IProcessProxy process, EventContext telemetryContext if (!string.IsNullOrEmpty(text)) { try - { + { SysbenchMetricsParser parser = new SysbenchMetricsParser(text); IList metrics = parser.Parse(); + string sysbenchVersion = null; + + var sysbenchVersionMetric = metrics.FirstOrDefault(); + if (sysbenchVersionMetric != null && sysbenchVersionMetric.Metadata.TryGetValue("sysbench_version", out var versionValue)) + { + sysbenchVersion = versionValue?.ToString(); + } + + if (!string.IsNullOrEmpty(sysbenchVersion)) + { + this.MetadataContract.Add("sysbench_version", sysbenchVersion, MetadataContractCategory.Dependencies); + } + + this.MetadataContract.Apply(telemetryContext); this.Logger.LogMetrics( toolName: "Sysbench", @@ -150,7 +164,8 @@ private void CaptureMetrics(IProcessProxy process, EventContext telemetryContext null, scenarioArguments: this.sysbenchLoggingArguments, this.Tags, - telemetryContext); + telemetryContext, + toolVersion: sysbenchVersion); } catch (Exception exc) { @@ -166,6 +181,14 @@ private Task ExecuteWorkloadAsync(EventContext telemetryContext, CancellationTok { using (BackgroundOperations profiling = BackgroundOperations.BeginProfiling(this, cancellationToken)) { + if (this.DatabaseSystem == "MySQL") + { + string mysqlVersion = await this.GetMySQLVersionAsync(telemetryContext, cancellationToken); + + this.MetadataContract.Add("mysql_version", mysqlVersion, MetadataContractCategory.Dependencies); + this.MetadataContract.Apply(telemetryContext); + } + if (this.Benchmark == BenchmarkName.OLTP) { if (this.Action == ClientAction.TruncateDatabase) @@ -182,6 +205,10 @@ private Task ExecuteWorkloadAsync(EventContext telemetryContext, CancellationTok { await this.PrepareOLTPMySQLDatabase(telemetryContext, cancellationToken); } + else if (this.Action == ClientAction.Cleanup) + { + await this.CleanUpDatabase(telemetryContext, cancellationToken); + } else { await this.RunOLTPWorkloadAsync(telemetryContext, cancellationToken); @@ -229,6 +256,33 @@ private async Task RunOLTPWorkloadAsync(EventContext telemetryContext, Cancellat } } + private async Task CleanUpDatabase(EventContext telemetryContext, CancellationToken cancellationToken) + { + int tableCount = GetTableCount(this.DatabaseScenario, this.TableCount, this.Workload); + + string serverIp = (this.GetLayoutClientInstances(ClientRole.Server, false) ?? Enumerable.Empty()) + .FirstOrDefault()?.IPAddress + ?? "localhost"; + + string sysbenchCleanupArguments = $"--dbName {this.DatabaseName} --databaseSystem {this.DatabaseSystem} --benchmark {this.Benchmark} --tableCount {tableCount} --hostIpAddress {serverIp}"; + + string script = $"{this.SysbenchPackagePath}/cleanup-database.py "; + + using (IProcessProxy process = await this.ExecuteCommandAsync( + SysbenchExecutor.PythonCommand, + script + sysbenchCleanupArguments, + this.SysbenchPackagePath, + telemetryContext, + cancellationToken)) + { + if (!cancellationToken.IsCancellationRequested) + { + await this.LogProcessDetailsAsync(process, telemetryContext, "Sysbench", logToFile: true); + process.ThrowIfErrored(process.StandardError.ToString(), ErrorReason.WorkloadFailed); + } + } + } + private async Task RunTPCCWorkloadAsync(EventContext telemetryContext, CancellationToken cancellationToken) { int tableCount = GetTableCount(this.Scenario, this.TableCount, this.Workload); @@ -288,7 +342,7 @@ private async Task PrepareOLTPMySQLDatabase(EventContext telemetryContext, Cance int threadCount = GetThreadCount(this.SystemManager, this.DatabaseScenario, this.Threads); int recordCount = GetRecordCount(this.SystemManager, this.DatabaseScenario, this.RecordCount); - this.sysbenchLoggingArguments = $"--dbName {this.DatabaseName} --databaseSystem {this.DatabaseSystem} --benchmark {this.Benchmark} --tableCount {tableCount} --recordCount {recordCount} --threadCount {threadCount}"; + this.sysbenchLoggingArguments = $"--dbName {this.DatabaseName} --databaseSystem {this.DatabaseSystem} --benchmark {this.Benchmark} --threadCount {threadCount} --tableCount {tableCount} --recordCount {recordCount}"; this.sysbenchPrepareArguments = $"{this.sysbenchLoggingArguments} --password {this.SuperUserPassword}"; string serverIp = (this.GetLayoutClientInstances(ClientRole.Server, false) ?? Enumerable.Empty()) @@ -315,5 +369,28 @@ private async Task PrepareOLTPMySQLDatabase(EventContext telemetryContext, Cance } } } + + /// + /// Returns MySQL Version. + /// + /// + /// + private async Task GetMySQLVersionAsync(EventContext telemetryContext, CancellationToken cancellationToken) + { + try + { + IProcessProxy mysqlversionprocess = await this.ExecuteCommandAsync("sudo", $"mysql -u {this.DatabaseName} -h {this.ServerIpAddress} -e \"SELECT VERSION();\"", Environment.CurrentDirectory, telemetryContext, cancellationToken); + string mysqlVersion = mysqlversionprocess.StandardOutput.ToString(); + + Regex regex = new Regex(@"(\d+\.\d+\.\d+)"); + Match match = regex.Match(mysqlVersion); + + return match.Success ? match.Groups[1].Value : string.Empty; + } + catch (Exception) + { + return string.Empty; + } + } } } diff --git a/src/VirtualClient/VirtualClient.Actions/Sysbench/SysbenchConfiguration.cs b/src/VirtualClient/VirtualClient.Actions/Sysbench/SysbenchConfiguration.cs index 87587facc3..caa31e733b 100644 --- a/src/VirtualClient/VirtualClient.Actions/Sysbench/SysbenchConfiguration.cs +++ b/src/VirtualClient/VirtualClient.Actions/Sysbench/SysbenchConfiguration.cs @@ -82,7 +82,7 @@ private async Task PrepareOLTPMySQLDatabase(EventContext telemetryContext, Cance int threadCount = GetThreadCount(this.SystemManager, this.DatabaseScenario, this.Threads); int recordCount = GetRecordCount(this.SystemManager, this.DatabaseScenario, this.RecordCount); - string sysbenchLoggingArguments = $"--dbName {this.DatabaseName} --databaseSystem {this.DatabaseSystem} --benchmark {this.Benchmark} --tableCount {tableCount} --recordCount {recordCount} --threadCount {threadCount}"; + string sysbenchLoggingArguments = $"--dbName {this.DatabaseName} --databaseSystem {this.DatabaseSystem} --benchmark {this.Benchmark} --threadCount {threadCount} --tableCount {tableCount} --recordCount {recordCount}"; this.sysbenchPrepareArguments = $"{sysbenchLoggingArguments} --password {this.SuperUserPassword}"; string serverIp = "localhost"; diff --git a/src/VirtualClient/VirtualClient.Actions/Sysbench/SysbenchExecutor.cs b/src/VirtualClient/VirtualClient.Actions/Sysbench/SysbenchExecutor.cs index b90d003818..e3ef89ebcf 100644 --- a/src/VirtualClient/VirtualClient.Actions/Sysbench/SysbenchExecutor.cs +++ b/src/VirtualClient/VirtualClient.Actions/Sysbench/SysbenchExecutor.cs @@ -135,7 +135,7 @@ public int? Threads } /// - /// Number of records per table. + /// Database system used. e.g: MYSQL, PostgreSQL. /// public string DatabaseSystem { @@ -417,7 +417,7 @@ protected void AddMetric(string arguments, IProcessProxy process, EventContext t List metrics = new List(); double duration = (process.ExitTime - process.StartTime).TotalMinutes; - metrics.Add(new Metric("PopulateDatabaseTime_Minutes ", duration, "minutes", MetricRelativity.LowerIsBetter)); + metrics.Add(new Metric("PopulateDatabaseDuration", duration, "minutes", MetricRelativity.LowerIsBetter)); this.Logger.LogMetrics( toolName: "Sysbench", @@ -532,9 +532,14 @@ internal class ClientAction public const string TruncateDatabase = nameof(TruncateDatabase); /// - /// Truncates all tables existing in database + /// Runs specified workload. /// public const string RunWorkload = nameof(RunWorkload); + + /// + /// Runs sysbench cleanup action. + /// + public const string Cleanup = nameof(Cleanup); } } } \ No newline at end of file diff --git a/src/VirtualClient/VirtualClient.Actions/Sysbench/SysbenchMetricsParser.cs b/src/VirtualClient/VirtualClient.Actions/Sysbench/SysbenchMetricsParser.cs index 26053cc35d..8d5e75b598 100644 --- a/src/VirtualClient/VirtualClient.Actions/Sysbench/SysbenchMetricsParser.cs +++ b/src/VirtualClient/VirtualClient.Actions/Sysbench/SysbenchMetricsParser.cs @@ -19,6 +19,7 @@ public class SysbenchMetricsParser : MetricsParser private const string ReconnectsPerSecond = "reconnects/sec"; private const string Second = "seconds"; private const string MilliSecond = "milliseconds"; + private Dictionary metricMetadata = new Dictionary(); /// /// Constructor for @@ -32,29 +33,32 @@ public SysbenchMetricsParser(string rawText) /// public override IList Parse() { + var match = Regex.Match(this.RawText, @"sysbench\s+([\d]+\.[\d]+\.[\d]+(?:-\w+)?)"); + string sysbenchversion = match.Success ? match.Groups[1].Value : string.Empty; + this.metricMetadata["sysbench_version"] = sysbenchversion; this.Preprocess(); List metrics = new List(); // Create list of Metrics Info List metricInfoList = new List() { - new MetricInfo("# read queries", string.Empty, MetricRelativity.HigherIsBetter), - new MetricInfo("# write queries", string.Empty, MetricRelativity.HigherIsBetter), - new MetricInfo("# other queries", string.Empty, MetricRelativity.HigherIsBetter), - new MetricInfo("# transactions", string.Empty, MetricRelativity.HigherIsBetter), - new MetricInfo("transactions/sec", TransactionsPerSecond, MetricRelativity.HigherIsBetter), - new MetricInfo("# queries", string.Empty, MetricRelativity.HigherIsBetter), - new MetricInfo("queries/sec", QueriesPerSecond, MetricRelativity.HigherIsBetter), - new MetricInfo("# ignored errors", string.Empty, MetricRelativity.HigherIsBetter), - new MetricInfo("ignored errors/sec", IgnoredErrorsPerSecond, MetricRelativity.HigherIsBetter), - new MetricInfo("# reconnects", string.Empty, MetricRelativity.HigherIsBetter), - new MetricInfo("reconnects/sec", ReconnectsPerSecond, MetricRelativity.HigherIsBetter), - new MetricInfo("elapsed time", Second, MetricRelativity.LowerIsBetter), - new MetricInfo("latency min", MilliSecond, MetricRelativity.LowerIsBetter), - new MetricInfo("latency avg", MilliSecond, MetricRelativity.LowerIsBetter), - new MetricInfo("latency max", MilliSecond, MetricRelativity.LowerIsBetter), - new MetricInfo("latency p95", MilliSecond, MetricRelativity.LowerIsBetter), - new MetricInfo("latency sum", MilliSecond, MetricRelativity.LowerIsBetter), + new MetricInfo("# read queries", string.Empty, MetricRelativity.HigherIsBetter, metadata: this.metricMetadata), + new MetricInfo("# write queries", string.Empty, MetricRelativity.HigherIsBetter, metadata: this.metricMetadata), + new MetricInfo("# other queries", string.Empty, MetricRelativity.HigherIsBetter, metadata: this.metricMetadata), + new MetricInfo("# transactions", string.Empty, MetricRelativity.HigherIsBetter, metadata: this.metricMetadata), + new MetricInfo("transactions/sec", TransactionsPerSecond, MetricRelativity.HigherIsBetter, metadata: this.metricMetadata), + new MetricInfo("# queries", string.Empty, MetricRelativity.HigherIsBetter, metadata: this.metricMetadata), + new MetricInfo("queries/sec", QueriesPerSecond, MetricRelativity.HigherIsBetter, metadata: this.metricMetadata), + new MetricInfo("# ignored errors", string.Empty, MetricRelativity.HigherIsBetter, metadata: this.metricMetadata), + new MetricInfo("ignored errors/sec", IgnoredErrorsPerSecond, MetricRelativity.HigherIsBetter, metadata: this.metricMetadata), + new MetricInfo("# reconnects", string.Empty, MetricRelativity.HigherIsBetter, metadata: this.metricMetadata), + new MetricInfo("reconnects/sec", ReconnectsPerSecond, MetricRelativity.HigherIsBetter, metadata: this.metricMetadata), + new MetricInfo("elapsed time", Second, MetricRelativity.LowerIsBetter, metadata: this.metricMetadata), + new MetricInfo("latency min", MilliSecond, MetricRelativity.LowerIsBetter, metadata: this.metricMetadata), + new MetricInfo("latency avg", MilliSecond, MetricRelativity.LowerIsBetter, metadata: this.metricMetadata), + new MetricInfo("latency max", MilliSecond, MetricRelativity.LowerIsBetter, metadata: this.metricMetadata), + new MetricInfo("latency p95", MilliSecond, MetricRelativity.LowerIsBetter, metadata: this.metricMetadata), + new MetricInfo("latency sum", MilliSecond, MetricRelativity.LowerIsBetter, metadata: this.metricMetadata), }; if (!string.IsNullOrEmpty(this.PreprocessedText)) @@ -84,7 +88,7 @@ public override IList Parse() { MetricInfo metricInfo = metricInfoList[metricInfoIndex]; Match m = mc[mcIndex]; - metrics.Add(new Metric($"{metricInfo.Name}", Convert.ToDouble(m.Value), metricInfo.Unit, metricInfo.Relativity)); + metrics.Add(new Metric($"{metricInfo.Name}", Convert.ToDouble(m.Value), metricInfo.Unit, metricInfo.Relativity, metadata: metricInfo.Metadata)); metricInfoIndex++; } @@ -107,11 +111,12 @@ protected override void Preprocess() // helper class that contains Metric Name, Unit, and Relativity private class MetricInfo { - public MetricInfo(string name, string unit, MetricRelativity relativity) + public MetricInfo(string name, string unit, MetricRelativity relativity, Dictionary metadata) { this.Name = name; this.Unit = unit; this.Relativity = relativity; + this.Metadata = metadata; } public string Name { get; set; } @@ -119,6 +124,8 @@ public MetricInfo(string name, string unit, MetricRelativity relativity) public string Unit { get; set; } public MetricRelativity Relativity { get; set; } + + public Dictionary Metadata { get; set; } } } } diff --git a/src/VirtualClient/VirtualClient.Actions/Sysbench/cleanup-database.py b/src/VirtualClient/VirtualClient.Actions/Sysbench/cleanup-database.py new file mode 100644 index 0000000000..e43175272a --- /dev/null +++ b/src/VirtualClient/VirtualClient.Actions/Sysbench/cleanup-database.py @@ -0,0 +1,22 @@ +import subprocess, argparse, os + +parser = argparse.ArgumentParser() + +parser.add_argument('-n', '--dbName', type=str, help='Database Name', required=True) +parser.add_argument('-y', '--databaseSystem', type=str, help='Database Type', required=False) +parser.add_argument('-b', '--benchmark', type=str, help="Benchmark Name", required=True) +parser.add_argument('-t', '--tableCount', type=str, help='Number of Tables', required=True) +parser.add_argument('-a', '--hostIpAddress', type=str, help="Host IP Address", required=True) + +args = parser.parse_args() +dbName = args.dbName +databaseSystem = args.databaseSystem +benchmark = args.benchmark +tableCount = args.tableCount +hostIp = args.hostIpAddress + +if databaseSystem == "MySQL": + if benchmark == "OLTP": + subprocess.run(f'sudo src/sysbench oltp_common --tables={tableCount} --mysql-db={dbName} --mysql-host={hostIp} cleanup',shell=True, check=True) +else: + parser.error("You are running on a database type that has not been onboarded to Virtual Client. Available options are: MySQL") \ No newline at end of file diff --git a/src/VirtualClient/VirtualClient.Actions/Sysbench/configure-workload-generator.py b/src/VirtualClient/VirtualClient.Actions/Sysbench/configure-workload-generator.py new file mode 100644 index 0000000000..18d5552536 --- /dev/null +++ b/src/VirtualClient/VirtualClient.Actions/Sysbench/configure-workload-generator.py @@ -0,0 +1,43 @@ +import subprocess +import argparse + +parser = argparse.ArgumentParser() + +parser.add_argument('-d', '--distro', type=str, help='Distribution', required=True) +parser.add_argument('-y', '--databaseSystem', type=str, help='Database System', required=False) +parser.add_argument('-p', '--packagePath', type=str, help='Workload Package Path', required=True) + +args = parser.parse_args() +distro = args.distro +databaseSystem = args.databaseSystem +path = args.packagePath + +subprocess.run('sudo git clone https://github.com/akopytov/sysbench.git', shell=True, check=True) +subprocess.run(f'sudo mv -v {path}/sysbench/* {path}', shell=True, check=True) + +subprocess.run('sudo git clone https://github.com/Percona-Lab/sysbench-tpcc.git', shell=True, check=True) +subprocess.run(f'sudo mv -v {path}/sysbench-tpcc/*.lua {path}/src/lua', shell=True, check=True) + +if distro == "Ubuntu" or distro == "Debian": + subprocess.run('sudo apt-get update -y', shell=True, check=True) + subprocess.run('sudo apt-get install make automake libtool pkg-config libaio-dev libmysqlclient-dev libssl-dev libpq-dev -y --quiet', shell=True, check=True) +elif distro == "CentOS8" or distro == "RHEL8" or distro == "Mariner": + subprocess.run('sudo dnf update -y', shell=True, check=True) + subprocess.run('sudo dnf install make automake libtool pkg-config libaio-devel, mariadb-devel, openssl-devel, postgresql-devel -y --quiet', shell=True, check=True) +elif distro == "CentOS7" or distro == "RHEL7": + subprocess.run('sudo yum update -y', shell=True, check=True) + subprocess.run('sudo yum install make automake libtool pkg-config libaio-devel, mariadb-devel, openssl-devel, postgresql-devel -y --quiet', shell=True, check=True) +elif distro == "SUSE": + subprocess.run('sudo zypper update', shell=True, check=True) + subprocess.run('sudo zypper --non-interactive install -y make automake libtool pkg-config libaio-dev, libmysqlclient-devel, openssl-devel, postgresql-devel', shell=True, check=True) +else: + parser.error("You are on a Linux distribution that has not been onboarded to Virtual Client.") + +subprocess.run('sudo sed -i "s/CREATE TABLE/CREATE TABLE IF NOT EXISTS/g" src/lua/oltp_common.lua', shell=True, check=True) +subprocess.run('sudo ./autogen.sh', shell=True, check=True) +if databaseSystem is None or databaseSystem == "MySQL": + subprocess.run('sudo ./configure', shell=True, check=True) +else: + subprocess.run('sudo ./configure --with-pgsql', shell=True, check=True) +subprocess.run('sudo make -j', shell=True, check=True) +subprocess.run('sudo make install', shell=True, check=True) \ No newline at end of file diff --git a/src/VirtualClient/VirtualClient.Actions/Sysbench/populate-database.py b/src/VirtualClient/VirtualClient.Actions/Sysbench/populate-database.py new file mode 100644 index 0000000000..21dc5dbbbc --- /dev/null +++ b/src/VirtualClient/VirtualClient.Actions/Sysbench/populate-database.py @@ -0,0 +1,101 @@ +import subprocess, argparse, os + +parser = argparse.ArgumentParser() + +parser.add_argument('-n', '--dbName', type=str, help='Database Name', required=True) +parser.add_argument('-y', '--databaseSystem', type=str, help='Database Type', required=False) +parser.add_argument('-b', '--benchmark', type=str, help="Benchmark Name", required=True) +parser.add_argument('-t', '--tableCount', type=str, help='Number of Tables', required=True) +parser.add_argument('-r', '--recordCount', type=str, help='Number of Records', required=False) +parser.add_argument('-u', '--warehouses', type=str, help='Warehouse Count', required=False) +parser.add_argument('-e', '--threadCount', type=str, help="Number of Threads", required=True) +parser.add_argument('--password', type=str, help="PostgreSQL Password", required=False) +parser.add_argument('--host', type=str, help="Database Server Host IP Address", required=False) + + +args = parser.parse_args() +dbName = args.dbName +databaseSystem = args.databaseSystem +benchmark = args.benchmark +warehouses = args.warehouses +tableCount = args.tableCount +recordCount = args.recordCount +threadCount = args.threadCount +password = args.password +host = args.host + +def add_host_if_needed(command_base, host, benchmark): + if host and benchmark != "TPCC": + if "sysbench" not in command_base: + return command_base.replace('-u', f'-h {host} -u') + else: + return f"{command_base} --mysql-host={host}" + else: + return command_base + +if databaseSystem == "MySQL": + if benchmark == "TPCC": + subprocess.run(f'sudo src/sysbench tpcc --tables={tableCount} --scale={warehouses} --threads={threadCount} --mysql-db={dbName} --use_fk=0 prepare', shell=True, check=True) + if int(warehouses) == 1: + for i in range(1,int(tableCount)+1): + table = str(i) + # drop idxs + subprocess.run(f'sudo mysql -u {dbName} {dbName} -e "DROP INDEX idx_customer{i} ON customer{i};"', shell=True, check=True) + subprocess.run(f'sudo mysql -u {dbName} {dbName} -e "DROP INDEX idx_orders{i} ON orders{i};"', shell=True, check=True) + subprocess.run(f'sudo mysql -u {dbName} {dbName} -e "DROP INDEX fkey_stock_2{i} ON stock{i};"', shell=True, check=True) + subprocess.run(f'sudo mysql -u {dbName} {dbName} -e "DROP INDEX fkey_order_line_2{i} ON order_line{i};"', shell=True, check=True) + subprocess.run(f'sudo mysql -u {dbName} {dbName} -e "DROP INDEX fkey_history_1{i} ON history{i};"', shell=True, check=True) + subprocess.run(f'sudo mysql -u {dbName} {dbName} -e "DROP INDEX fkey_history_2{i} ON history{i};"', shell=True, check=True) + + # truncate, to make distributing faster + subprocess.run(f'sudo mysql -u {dbName} {dbName} -e "TRUNCATE TABLE warehouse{i};"', shell=True, check=True) + subprocess.run(f'sudo mysql -u {dbName} {dbName} -e "TRUNCATE TABLE district{i};"', shell=True, check=True) + subprocess.run(f'sudo mysql -u {dbName} {dbName} -e "TRUNCATE TABLE customer{i};"', shell=True, check=True) + subprocess.run(f'sudo mysql -u {dbName} {dbName} -e "TRUNCATE TABLE history{i};"', shell=True, check=True) + subprocess.run(f'sudo mysql -u {dbName} {dbName} -e "TRUNCATE TABLE orders{i};"', shell=True, check=True) + subprocess.run(f'sudo mysql -u {dbName} {dbName} -e "TRUNCATE TABLE new_orders{i};"', shell=True, check=True) + subprocess.run(f'sudo mysql -u {dbName} {dbName} -e "TRUNCATE TABLE order_line{i};"', shell=True, check=True) + subprocess.run(f'sudo mysql -u {dbName} {dbName} -e "TRUNCATE TABLE stock{i};"', shell=True, check=True) + subprocess.run(f'sudo mysql -u {dbName} {dbName} -e "TRUNCATE TABLE item{i};"', shell=True, check=True) + else: + command_base = f'sudo src/sysbench oltp_common --tables={tableCount} --table-size={recordCount} --threads={threadCount} --mysql-db={dbName} prepare' + command = add_host_if_needed(command_base, host, benchmark) + subprocess.run(command, shell=True, check=True) + if int(recordCount) == 1: + for i in range(1, int(tableCount) + 1): + drop_index_command = f'sudo mysql -u {dbName} {dbName} -e "DROP INDEX k_{i} ON sbtest{i};"' + drop_index_command = add_host_if_needed(drop_index_command, host, benchmark) + subprocess.run(drop_index_command, shell=True, check=True) +elif databaseSystem == "PostgreSQL": + os.environ['PGPASSWORD'] = password + if benchmark == "TPCC": + subprocess.run(f'sudo src/sysbench tpcc --db-driver=pgsql --tables={tableCount} --scale={warehouses} --threads={threadCount} --pgsql-user=postgres --pgsql-password={password} --pgsql-db={dbName} --use_fk=0 prepare', shell=True, check=True) + if int(warehouses) == 1: + for i in range(1,int(tableCount)+1): + table = str(i) + # drop idxs + subprocess.run(f'psql -U postgres -d {dbName} -c "DROP INDEX IF EXISTS idx_customer{i};"', shell=True, check=True) + subprocess.run(f'psql -U postgres -d {dbName} -c "DROP INDEX IF EXISTS idx_orders{i};"', shell=True, check=True) + subprocess.run(f'psql -U postgres -d {dbName} -c "DROP INDEX IF EXISTS fkey_stock_2{i};"', shell=True, check=True) + subprocess.run(f'psql -U postgres -d {dbName} -c "DROP INDEX IF EXISTS fkey_order_line_2{i};"', shell=True, check=True) + subprocess.run(f'psql -U postgres -d {dbName} -c "DROP INDEX IF EXISTS fkey_history_1{i};"', shell=True, check=True) + subprocess.run(f'psql -U postgres -d {dbName} -c "DROP INDEX IF EXISTS fkey_history_2{i};"', shell=True, check=True) + + # truncate, to make distributing faster + subprocess.run(f'psql -U postgres -d {dbName} -c "TRUNCATE TABLE warehouse{i};"', shell=True, check=True) + subprocess.run(f'psql -U postgres -d {dbName} -c "TRUNCATE TABLE district{i};"', shell=True, check=True) + subprocess.run(f'psql -U postgres -d {dbName} -c "TRUNCATE TABLE customer{i};"', shell=True, check=True) + subprocess.run(f'psql -U postgres -d {dbName} -c "TRUNCATE TABLE history{i};"', shell=True, check=True) + subprocess.run(f'psql -U postgres -d {dbName} -c "TRUNCATE TABLE orders{i};"', shell=True, check=True) + subprocess.run(f'psql -U postgres -d {dbName} -c "TRUNCATE TABLE new_orders{i};"', shell=True, check=True) + subprocess.run(f'psql -U postgres -d {dbName} -c "TRUNCATE TABLE order_line{i};"', shell=True, check=True) + subprocess.run(f'psql -U postgres -d {dbName} -c "TRUNCATE TABLE stock{i};"', shell=True, check=True) + subprocess.run(f'psql -U postgres -d {dbName} -c "TRUNCATE TABLE item{i};"', shell=True, check=True) + else: + subprocess.run(f'sudo src/sysbench oltp_common --db-driver=pgsql --tables={tableCount} --table-size={recordCount} --threads={threadCount} --pgsql-user=postgres --pgsql-password={password} --pgsql-db={dbName} prepare', shell=True, check=True) + if int(recordCount) == 1: + for i in range(1,int(tableCount)+1): + table = str(i) + subprocess.run(f'psql -U postgres -d {dbName} -c "DROP INDEX IF EXISTS k_{i};"', shell=True, check=True) +else: + parser.error("You are running on a database system type that has not been onboarded to Virtual Client. Available options are: MySQL, PostgreSQL") \ No newline at end of file diff --git a/src/VirtualClient/VirtualClient.Actions/Sysbench/run-workload.py b/src/VirtualClient/VirtualClient.Actions/Sysbench/run-workload.py new file mode 100644 index 0000000000..aa06b7fa76 --- /dev/null +++ b/src/VirtualClient/VirtualClient.Actions/Sysbench/run-workload.py @@ -0,0 +1,41 @@ +import subprocess, argparse, os + +parser = argparse.ArgumentParser() + +parser.add_argument('-n', '--dbName', type=str, help='Database Name', required=True) +parser.add_argument('-y', '--databaseSystem', type=str, help='Database Type', required=False) +parser.add_argument('-w', '--workload', type=str, help="Workload Name", required=True) +parser.add_argument('-b', '--benchmark', type=str, help="Benchmark Name", required=True) +parser.add_argument('-t', '--tableCount', type=str, help='Number of Tables', required=True) +parser.add_argument('-r', '--recordCount', type=str, help='Number of Records', required=False) +parser.add_argument('-u', '--warehouses', type=str, help='Warehouse Count', required=False) +parser.add_argument('-e', '--threadCount', type=str, help="Number of Threads", required=True) +parser.add_argument('-a', '--hostIpAddress', type=str, help="Host IP Address", required=True) +parser.add_argument('-d', '--durationSecs', type=str, help="Duration in Seconds", required=True) +parser.add_argument('--password', type=str, help="PostgreSQL Password", required=False) + +args = parser.parse_args() +dbName = args.dbName +databaseSystem = args.databaseSystem +workload = args.workload +benchmark = args.benchmark +tableCount = args.tableCount +recordCount = args.recordCount +warehouses = args.warehouses +threadCount = args.threadCount +hostIp = args.hostIpAddress +durationSecs = args.durationSecs +password = args.password + +if databaseSystem == "MySQL": + if benchmark == "TPCC": + subprocess.run(f'sudo src/sysbench tpcc --tables={tableCount} --scale={warehouses} --threads={threadCount} --mysql-db={dbName} --mysql-host={hostIp} --time={durationSecs} run', shell=True, check=True) + else: + subprocess.run(f'sudo src/sysbench {workload} --tables={tableCount} --table-size={recordCount} --threads={threadCount} --mysql-db={dbName} --mysql-host={hostIp} --time={durationSecs} run', shell=True, check=True) +elif databaseSystem == "PostgreSQL": + if benchmark == "TPCC": + subprocess.run(f'sudo src/sysbench tpcc --db-driver=pgsql --tables={tableCount} --scale={warehouses} --threads={threadCount} --pgsql-user=postgres --pgsql-password={password} --pgsql-db={dbName} --pgsql-host={hostIp} --time={durationSecs} run', shell=True, check=True) + else: + subprocess.run(f'sudo src/sysbench {workload} --db-driver=pgsql --tables={tableCount} --table-size={recordCount} --threads={threadCount} --pgsql-user=postgres --pgsql-password={password} --pgsql-db={dbName} --pgsql-host={hostIp} --time={durationSecs} run', shell=True, check=True) +else: + parser.error("You are running on a database type that has not been onboarded to Virtual Client. Available options are: MySQL, PostgreSQL") \ No newline at end of file diff --git a/src/VirtualClient/VirtualClient.Dependencies.UnitTests/MySqlServer/MySQLServerConfigurationTests.cs b/src/VirtualClient/VirtualClient.Dependencies.UnitTests/MySqlServer/MySQLServerConfigurationTests.cs index 58a13b9019..1fda0b7fad 100644 --- a/src/VirtualClient/VirtualClient.Dependencies.UnitTests/MySqlServer/MySQLServerConfigurationTests.cs +++ b/src/VirtualClient/VirtualClient.Dependencies.UnitTests/MySqlServer/MySQLServerConfigurationTests.cs @@ -273,7 +273,7 @@ public async Task MySQLConfigurationExecutesTheExpectedProcessForRaiseMaxStateme string[] expectedCommands = { - $"python3 {this.packagePath}/set-global-variables.py --variables MAX_PREPARED_STMT_COUNT=100000;MAX_CONNECTIONS=1024", + $"python3 {this.packagePath}/set-global-variables.py --variables \"MAX_PREPARED_STMT_COUNT=100000;MAX_CONNECTIONS=1024\"", }; int commandNumber = 0; diff --git a/src/VirtualClient/VirtualClient.Dependencies/MySqlServer/MySqlServerConfiguration.cs b/src/VirtualClient/VirtualClient.Dependencies/MySqlServer/MySqlServerConfiguration.cs index 4ee17e5f28..19886c1021 100644 --- a/src/VirtualClient/VirtualClient.Dependencies/MySqlServer/MySqlServerConfiguration.cs +++ b/src/VirtualClient/VirtualClient.Dependencies/MySqlServer/MySqlServerConfiguration.cs @@ -165,15 +165,14 @@ await this.CreateMySQLServerDatabaseAsync(telemetryContext, cancellationToken) await this.DistributeMySQLDatabaseAsync(telemetryContext, cancellationToken) .ConfigureAwait(false); break; + case ConfigurationAction.SetGlobalVariables: + await this.SetMySQLGlobalVariableAsync(telemetryContext, cancellationToken) + .ConfigureAwait(false); + break; } await this.stateManager.SaveStateAsync(stateId, new ConfigurationState(this.Action), cancellationToken); } - else if (this.Action == ConfigurationAction.SetGlobalVariables) - { - await this.SetMySQLGlobalVariableAsync(telemetryContext, cancellationToken) - .ConfigureAwait(false); - } } } diff --git a/src/VirtualClient/VirtualClient.Main/profiles/PERF-MYSQL-SYSBENCH-OLTP.json b/src/VirtualClient/VirtualClient.Main/profiles/PERF-MYSQL-SYSBENCH-OLTP.json index 58c7745c19..fa554bf928 100644 --- a/src/VirtualClient/VirtualClient.Main/profiles/PERF-MYSQL-SYSBENCH-OLTP.json +++ b/src/VirtualClient/VirtualClient.Main/profiles/PERF-MYSQL-SYSBENCH-OLTP.json @@ -181,7 +181,7 @@ "Parameters": { "Scenario": "DownloadSysbenchPackage", "BlobContainer": "packages", - "BlobName": "sysbench-1.0.20.rev2.zip", + "BlobName": "sysbench-1.0.20.rev3.zip", "PackageName": "sysbench", "Extract": true } diff --git a/src/VirtualClient/VirtualClient.Main/profiles/PERF-POSTGRESQL-SYSBENCH-OLTP.json b/src/VirtualClient/VirtualClient.Main/profiles/PERF-POSTGRESQL-SYSBENCH-OLTP.json index 8895ebf569..18a75f445c 100644 --- a/src/VirtualClient/VirtualClient.Main/profiles/PERF-POSTGRESQL-SYSBENCH-OLTP.json +++ b/src/VirtualClient/VirtualClient.Main/profiles/PERF-POSTGRESQL-SYSBENCH-OLTP.json @@ -182,7 +182,7 @@ "Parameters": { "Scenario": "DownloadSysbenchPackage", "BlobContainer": "packages", - "BlobName": "sysbench-1.0.20.rev2.zip", + "BlobName": "sysbench-1.0.20.rev3.zip", "PackageName": "sysbench", "Extract": true }