From 2bb153f40a1a560167edb23f1bcbf67a5ce1c11d Mon Sep 17 00:00:00 2001
From: saurabh-saraswat <85618497+saurabh-saraswat@users.noreply.github.com>
Date: Sat, 11 Oct 2025 18:20:18 +0530
Subject: [PATCH 1/4] Add files via upload
Signed-off-by: saurabh-saraswat <85618497+saurabh-saraswat@users.noreply.github.com>
---
.../Cassandra/CassandraExecutor.cs | 161 ++++++++++++++++++
.../Cassandra/CassandraMetricsParser.cs | 105 ++++++++++++
2 files changed, 266 insertions(+)
create mode 100644 src/VirtualClient/VirtualClient.Actions/Cassandra/CassandraExecutor.cs
create mode 100644 src/VirtualClient/VirtualClient.Actions/Cassandra/CassandraMetricsParser.cs
diff --git a/src/VirtualClient/VirtualClient.Actions/Cassandra/CassandraExecutor.cs b/src/VirtualClient/VirtualClient.Actions/Cassandra/CassandraExecutor.cs
new file mode 100644
index 0000000000..20bc7f7488
--- /dev/null
+++ b/src/VirtualClient/VirtualClient.Actions/Cassandra/CassandraExecutor.cs
@@ -0,0 +1,161 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace VirtualClient.Actions
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.IO.Abstractions;
+ using System.Runtime.InteropServices;
+ using System.Threading;
+ using System.Threading.Tasks;
+ using Microsoft.Extensions.DependencyInjection;
+ using Microsoft.Extensions.Logging;
+ using VirtualClient.Common;
+ using VirtualClient.Common.Extensions;
+ using VirtualClient.Common.Platform;
+ using VirtualClient.Common.Telemetry;
+ using VirtualClient.Contracts;
+ using VirtualClient.Contracts.Metadata;
+
+ ///
+ /// Executor.
+ ///
+ public class CassandraExecutor : VirtualClientComponent
+ {
+ private IFileSystem fileSystem;
+ private IPackageManager packageManager;
+ private IStateManager stateManager;
+ private ISystemManagement systemManager;
+ // private string cassandraDirectory = @".";
+
+ ///
+ /// Executor.
+ ///
+ public CassandraExecutor(IServiceCollection dependencies, IDictionary parameters)
+ : base(dependencies, parameters)
+ {
+ this.systemManager = this.Dependencies.GetService();
+ this.packageManager = this.systemManager.PackageManager;
+ this.stateManager = this.systemManager.StateManager;
+ this.fileSystem = this.systemManager.FileSystem;
+ }
+
+ ///
+ /// Cassandra space separated input files or directories
+ ///
+ public string InputFilesOrDirs
+ {
+ get
+ {
+ this.Parameters.TryGetValue(nameof(CassandraExecutor.InputFilesOrDirs), out IConvertible inputFilesOrDirs);
+ return inputFilesOrDirs?.ToString();
+ }
+ }
+
+ ///
+ /// Executes the workload.
+ ///
+ protected override async Task ExecuteAsync(EventContext telemetryContext, CancellationToken cancellationToken)
+ {
+ try
+ {
+ // Command and parameters specified in the workload configuration
+ // Execute the command
+ using (BackgroundOperations profiling = BackgroundOperations.BeginProfiling(this, cancellationToken))
+ {
+ // this.Logger.LogInformation($"inside using profiling");
+ string command = this.Parameters.GetValue("command");
+ string argument = this.Parameters.GetValue("parameters");
+ using (IProcessProxy process = await this.ExecuteCommandAsync(command, argument, ".", telemetryContext, cancellationToken)
+ .ConfigureAwait(false))
+ {
+ // this.Logger.LogInformation($"inside using process");
+ if (!cancellationToken.IsCancellationRequested)
+ {
+ await this.LogProcessDetailsAsync(process, telemetryContext, "Cassandra", logToFile: true);
+ process.ThrowIfWorkloadFailed();
+ this.CaptureMetrics(process, telemetryContext, argument);
+ }
+
+ if (process.ExitCode != 0)
+ {
+ this.Logger.LogInformation($"inside if block");
+ throw new WorkloadException($"Command failed with exit code {process.ExitCode}.");
+ }
+
+ // this.Logger.LogInformation($"Command output: {output}");
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ // Log the error and rethrow
+ this.Logger.LogInformation($"catch exception : {ex.Message}");
+ throw new WorkloadException($"Failed to parse cassandra output: {ex.Message}", ex);
+ }
+ }
+
+ private async Task ExecuteCommandAsync(string pathToExe, string commandLineArguments, string workingDirectory, CancellationToken cancellationtoken)
+ {
+ if (!cancellationToken.IsCancellationRequested)
+ {
+ this.Logger.LogTraceMessage($"Executing process '{pathToExe}' '{commandLineArguments}' at directory '{workingDirectory}'.");
+
+ EventContext telemetryContext = EventContext.Persisted()
+ .AddContext("command", pathToExe)
+ .AddContext("commandArguments", commandLineArguments);
+
+ await this.Logger.LogMessageAsync($"{nameof(YcsbExecutor)}.ExecuteProcess", telemetryContext, async () =>
+ {
+ DateTime start = DateTime.Now;
+ using (IProcessProxy process = this.systemManager.ProcessManager.CreateElevatedProcess(this.Platform, pathToExe, commandLineArguments, workingDirectory))
+ {
+ this.CleanupTasks.Add(() => process.SafeKill());
+ await process.StartAndWaitAsync(cancellationToken)
+ .ConfigureAwait(false);
+
+ if (!cancellationToken.IsCancellationRequested)
+ {
+ await this.LogProcessDetailsAsync(process, telemetryContext)
+ .ConfigureAwait(false);
+
+ process.ThrowIfErrored(errorReason: ErrorReason.WorkloadFailed);
+ }
+ }
+ }).ConfigureAwait(false);
+ }
+ }
+
+ private void CaptureMetrics(IProcessProxy process, EventContext telemetryContext, string commandArguments)
+ {
+ process.ThrowIfNull(nameof(process));
+
+ this.MetadataContract.AddForScenario(
+ "cassandra",
+ commandArguments,
+ toolVersion: null);
+
+ this.MetadataContract.Apply(telemetryContext);
+
+ CassandraMetricsParser parser = new CassandraMetricsParser(process.StandardOutput.ToString());
+ IList metrics = parser.Parse();
+
+ this.Logger.LogMetrics(
+ "cassandra",
+ this.Scenario,
+ process.StartTime,
+ process.ExitTime,
+ metrics,
+ null,
+ commandArguments,
+ this.Tags,
+ telemetryContext);
+ }
+ }
+}
+
+
+
+
diff --git a/src/VirtualClient/VirtualClient.Actions/Cassandra/CassandraMetricsParser.cs b/src/VirtualClient/VirtualClient.Actions/Cassandra/CassandraMetricsParser.cs
new file mode 100644
index 0000000000..47e85bbd03
--- /dev/null
+++ b/src/VirtualClient/VirtualClient.Actions/Cassandra/CassandraMetricsParser.cs
@@ -0,0 +1,105 @@
+namespace VirtualClient.Actions
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Data;
+ using System.Text.RegularExpressions;
+ using VirtualClient.Common.Contracts;
+ using VirtualClient.Contracts;
+ using DataTableExtensions = VirtualClient.Contracts.DataTableExtensions;
+
+ ///
+ /// Parser for Cassandra output document.
+ ///
+ public class CassandraMetricsParser : MetricsParser
+ {
+ private static readonly Regex OpRateRegex = new Regex(@"Op rate\s+:\s+([\d,]+) op/s", RegexOptions.Multiline);
+ private static readonly Regex PartitionRateRegex = new Regex(@"Partition rate\s+:\s+([\d,]+) pk/s", RegexOptions.Multiline);
+ private static readonly Regex RowRateRegex = new Regex(@"Row rate\s+:\s+([\d,]+) row/s", RegexOptions.Multiline);
+ private static readonly Regex LatencyMeanRegex = new Regex(@"Latency mean\s+:\s+([\d.]+) ms", RegexOptions.Multiline);
+ private static readonly Regex LatencyMaxRegex = new Regex(@"Latency max\s+:\s+([\d.]+) ms", RegexOptions.Multiline);
+ private static readonly Regex TotalErrorsRegex = new Regex(@"Total errors\s+:\s+([\d,]+)", RegexOptions.Multiline);
+ private static readonly Regex TotalOperationTimeRegex = new Regex(@"Total operation time\s+:\s+(\d{2}:\d{2}:\d{2})", RegexOptions.Multiline);
+
+ ///
+ /// constructor for .
+ ///
+ /// Raw text to parse.
+ public CassandraMetricsParser(string rawText)
+ : base(rawText)
+ {
+ }
+
+ ///
+ /// List of parsed metrics from the YCSB output.
+ ///
+ public List Metrics { get; set; } = new List();
+
+ ///
+ public override IList Parse()
+ {
+ this.Preprocess();
+ this.ThrowIfInvalidOutputFormat();
+ this.ExtractMetrics();
+
+ return this.Metrics;
+ }
+
+ ///
+ protected override void Preprocess()
+ {
+ // Normalize the text to ensure consistent formatting.
+ this.PreprocessedText = Regex.Replace(this.RawText, "\r\n", "\n");
+ this.PreprocessedText = Regex.Replace(this.PreprocessedText, "\n\n", "\n"); // Consolidate multiple newlines
+ }
+
+ ///
+ private void ThrowIfInvalidOutputFormat()
+ {
+ if (string.IsNullOrWhiteSpace(this.PreprocessedText))
+ {
+ throw new SchemaException("The Cassandra Stress output has incorrect format for parsing: empty or null text.");
+ }
+
+ if (!OpRateRegex.IsMatch(this.PreprocessedText))
+ {
+ throw new SchemaException("The Cassandra Stress output has incorrect format for parsing: missing key metrics.");
+ }
+ }
+
+ private void ExtractMetrics()
+ {
+ this.ExtractMetric(OpRateRegex, "Op Rate", "ops/s");
+ this.ExtractMetric(PartitionRateRegex, "Partition Rate", "pk/s");
+ this.ExtractMetric(RowRateRegex, "Row Rate", "row/s");
+ this.ExtractMetric(LatencyMeanRegex, "Latency Mean", "ms");
+ this.ExtractMetric(LatencyMaxRegex, "Latency Max", "ms");
+ this.ExtractMetric(TotalErrorsRegex, "Total Errors", "count");
+ // this.ExtractMetric(TotalOperationTimeRegex, "Total Operation Time", "time");
+ var match = TotalOperationTimeRegex.Match(this.PreprocessedText);
+ if (match.Success)
+ {
+ if (TimeSpan.TryParse(match.Groups[1].Value, out TimeSpan operationTime))
+ {
+ this.Metrics.Add(new Metric("Total Operation Time", operationTime.ToString(@"hh\:mm\:ss"), "hh:mm:ss"));
+ }
+ else
+ {
+ throw new FormatException($"Invalid operation time format: {match.Groups[1].Value}");
+ }
+ }
+
+ }
+
+ private void ExtractMetric(Regex regex, string metricName, string unit)
+ {
+ var match = regex.Match(this.PreprocessedText);
+ if (match.Success)
+ {
+ this.Metrics.Add(new Metric(metricName, match.Groups[1].Value, unit));
+ }
+ }
+ }
+}
+
+
From b62c6829aa1337ce5d5bfc9e72df35a6ca449b37 Mon Sep 17 00:00:00 2001
From: saurabh-saraswat <85618497+saurabh-saraswat@users.noreply.github.com>
Date: Sat, 11 Oct 2025 18:22:58 +0530
Subject: [PATCH 2/4] Add files via upload
Signed-off-by: saurabh-saraswat <85618497+saurabh-saraswat@users.noreply.github.com>
---
.../profiles/PERF-CASSANDRA.json | 27 +++++++++++++++++++
1 file changed, 27 insertions(+)
create mode 100644 src/VirtualClient/VirtualClient.Main/profiles/PERF-CASSANDRA.json
diff --git a/src/VirtualClient/VirtualClient.Main/profiles/PERF-CASSANDRA.json b/src/VirtualClient/VirtualClient.Main/profiles/PERF-CASSANDRA.json
new file mode 100644
index 0000000000..036b0f10e8
--- /dev/null
+++ b/src/VirtualClient/VirtualClient.Main/profiles/PERF-CASSANDRA.json
@@ -0,0 +1,27 @@
+{
+ "Description": "Cassandra Workload",
+ "Metadata": {
+ "RecommendedMinimumExecutionTime": "00:05:00",
+ "SupportedPlatforms": "linux-x64,linux-arm64,win-x64,win-arm64",
+ "SupportedOperatingSystems": "CBL-Mariner,CentOS,Debian,RedHat,Suse,Ubuntu,Windows"
+ },
+ "Actions": [
+ {
+ "Type": "CassandraExecutor",
+ "Parameters": {
+ "Scenario": "Cassandra stress test for READ",
+ "command": "cassandra-stress",
+ "parameters": "read n=20000 -rate threads=50"
+ }
+ }
+ ],
+ "Dependencies": [
+ {
+ "Type": "LinuxPackageInstallation",
+ "Parameters": {
+ "Scenario": "InstallCassandraPackage",
+ "Packages-Apt": "cassandra"
+ }
+ }
+ ]
+}
From 9df51e3099f41e7134c24a30947a38d263df6935 Mon Sep 17 00:00:00 2001
From: Saurabh Saraswat
Date: Mon, 13 Oct 2025 12:16:48 +0000
Subject: [PATCH 3/4] Fixed the errors for Cassandra Workload
---
.../Cassandra/CassandraExecutor.cs | 315 +++++++++---------
.../Cassandra/CassandraMetricsParser.cs | 208 ++++++------
.../profiles/PERF-CASSANDRA.json | 55 +--
3 files changed, 285 insertions(+), 293 deletions(-)
diff --git a/src/VirtualClient/VirtualClient.Actions/Cassandra/CassandraExecutor.cs b/src/VirtualClient/VirtualClient.Actions/Cassandra/CassandraExecutor.cs
index 20bc7f7488..f1118f460e 100644
--- a/src/VirtualClient/VirtualClient.Actions/Cassandra/CassandraExecutor.cs
+++ b/src/VirtualClient/VirtualClient.Actions/Cassandra/CassandraExecutor.cs
@@ -1,161 +1,154 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-namespace VirtualClient.Actions
-{
- using System;
- using System.Collections.Generic;
- using System.Diagnostics;
- using System.IO.Abstractions;
- using System.Runtime.InteropServices;
- using System.Threading;
- using System.Threading.Tasks;
- using Microsoft.Extensions.DependencyInjection;
- using Microsoft.Extensions.Logging;
- using VirtualClient.Common;
- using VirtualClient.Common.Extensions;
- using VirtualClient.Common.Platform;
- using VirtualClient.Common.Telemetry;
- using VirtualClient.Contracts;
- using VirtualClient.Contracts.Metadata;
-
- ///
- /// Executor.
- ///
- public class CassandraExecutor : VirtualClientComponent
- {
- private IFileSystem fileSystem;
- private IPackageManager packageManager;
- private IStateManager stateManager;
- private ISystemManagement systemManager;
- // private string cassandraDirectory = @".";
-
- ///
- /// Executor.
- ///
- public CassandraExecutor(IServiceCollection dependencies, IDictionary parameters)
- : base(dependencies, parameters)
- {
- this.systemManager = this.Dependencies.GetService();
- this.packageManager = this.systemManager.PackageManager;
- this.stateManager = this.systemManager.StateManager;
- this.fileSystem = this.systemManager.FileSystem;
- }
-
- ///
- /// Cassandra space separated input files or directories
- ///
- public string InputFilesOrDirs
- {
- get
- {
- this.Parameters.TryGetValue(nameof(CassandraExecutor.InputFilesOrDirs), out IConvertible inputFilesOrDirs);
- return inputFilesOrDirs?.ToString();
- }
- }
-
- ///
- /// Executes the workload.
- ///
- protected override async Task ExecuteAsync(EventContext telemetryContext, CancellationToken cancellationToken)
- {
- try
- {
- // Command and parameters specified in the workload configuration
- // Execute the command
- using (BackgroundOperations profiling = BackgroundOperations.BeginProfiling(this, cancellationToken))
- {
- // this.Logger.LogInformation($"inside using profiling");
- string command = this.Parameters.GetValue("command");
- string argument = this.Parameters.GetValue("parameters");
- using (IProcessProxy process = await this.ExecuteCommandAsync(command, argument, ".", telemetryContext, cancellationToken)
- .ConfigureAwait(false))
- {
- // this.Logger.LogInformation($"inside using process");
- if (!cancellationToken.IsCancellationRequested)
- {
- await this.LogProcessDetailsAsync(process, telemetryContext, "Cassandra", logToFile: true);
- process.ThrowIfWorkloadFailed();
- this.CaptureMetrics(process, telemetryContext, argument);
- }
-
- if (process.ExitCode != 0)
- {
- this.Logger.LogInformation($"inside if block");
- throw new WorkloadException($"Command failed with exit code {process.ExitCode}.");
- }
-
- // this.Logger.LogInformation($"Command output: {output}");
- }
- }
- }
- catch (Exception ex)
- {
- // Log the error and rethrow
- this.Logger.LogInformation($"catch exception : {ex.Message}");
- throw new WorkloadException($"Failed to parse cassandra output: {ex.Message}", ex);
- }
- }
-
- private async Task ExecuteCommandAsync(string pathToExe, string commandLineArguments, string workingDirectory, CancellationToken cancellationtoken)
- {
- if (!cancellationToken.IsCancellationRequested)
- {
- this.Logger.LogTraceMessage($"Executing process '{pathToExe}' '{commandLineArguments}' at directory '{workingDirectory}'.");
-
- EventContext telemetryContext = EventContext.Persisted()
- .AddContext("command", pathToExe)
- .AddContext("commandArguments", commandLineArguments);
-
- await this.Logger.LogMessageAsync($"{nameof(YcsbExecutor)}.ExecuteProcess", telemetryContext, async () =>
- {
- DateTime start = DateTime.Now;
- using (IProcessProxy process = this.systemManager.ProcessManager.CreateElevatedProcess(this.Platform, pathToExe, commandLineArguments, workingDirectory))
- {
- this.CleanupTasks.Add(() => process.SafeKill());
- await process.StartAndWaitAsync(cancellationToken)
- .ConfigureAwait(false);
-
- if (!cancellationToken.IsCancellationRequested)
- {
- await this.LogProcessDetailsAsync(process, telemetryContext)
- .ConfigureAwait(false);
-
- process.ThrowIfErrored(errorReason: ErrorReason.WorkloadFailed);
- }
- }
- }).ConfigureAwait(false);
- }
- }
-
- private void CaptureMetrics(IProcessProxy process, EventContext telemetryContext, string commandArguments)
- {
- process.ThrowIfNull(nameof(process));
-
- this.MetadataContract.AddForScenario(
- "cassandra",
- commandArguments,
- toolVersion: null);
-
- this.MetadataContract.Apply(telemetryContext);
-
- CassandraMetricsParser parser = new CassandraMetricsParser(process.StandardOutput.ToString());
- IList metrics = parser.Parse();
-
- this.Logger.LogMetrics(
- "cassandra",
- this.Scenario,
- process.StartTime,
- process.ExitTime,
- metrics,
- null,
- commandArguments,
- this.Tags,
- telemetryContext);
- }
- }
-}
-
-
-
-
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace VirtualClient.Actions
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.IO.Abstractions;
+ using System.Runtime.InteropServices;
+ using System.Threading;
+ using System.Threading.Tasks;
+ using Microsoft.Extensions.DependencyInjection;
+ using Microsoft.Extensions.Logging;
+ using VirtualClient.Common;
+ using VirtualClient.Common.Extensions;
+ using VirtualClient.Common.Platform;
+ using VirtualClient.Common.Telemetry;
+ using VirtualClient.Contracts;
+ using VirtualClient.Contracts.Metadata;
+
+ ///
+ /// Executor.
+ ///
+ public class CassandraExecutor : VirtualClientComponent
+ {
+ private IFileSystem fileSystem;
+ private IPackageManager packageManager;
+ private IStateManager stateManager;
+ private ISystemManagement systemManager;
+ // private string cassandraDirectory = @".";
+
+ ///
+ /// Executor.
+ ///
+ public CassandraExecutor(IServiceCollection dependencies, IDictionary parameters)
+ : base(dependencies, parameters)
+ {
+ this.systemManager = this.Dependencies.GetService();
+ this.packageManager = this.systemManager.PackageManager;
+ this.stateManager = this.systemManager.StateManager;
+ this.fileSystem = this.systemManager.FileSystem;
+ }
+
+ ///
+ /// Cassandra space separated input files or directories
+ ///
+ public string InputFilesOrDirs
+ {
+ get
+ {
+ this.Parameters.TryGetValue(nameof(CassandraExecutor.InputFilesOrDirs), out IConvertible inputFilesOrDirs);
+ return inputFilesOrDirs?.ToString();
+ }
+ }
+
+ ///
+ /// Executes the workload.
+ ///
+ protected override async Task ExecuteAsync(EventContext telemetryContext, CancellationToken cancellationToken)
+ {
+ try
+ {
+ // Command and parameters specified in the workload configuration
+ // Execute the command
+ using (BackgroundOperations profiling = BackgroundOperations.BeginProfiling(this, cancellationToken))
+ {
+ // this.Logger.LogInformation($"inside using profiling");
+ string command = this.Parameters.GetValue("command");
+ string argument = this.Parameters.GetValue("parameters");
+ using (IProcessProxy process = await this.ExecuteCommandAsync(command, argument, ".", telemetryContext, cancellationToken)
+ .ConfigureAwait(false))
+ {
+ if (!cancellationToken.IsCancellationRequested)
+ {
+ await this.LogProcessDetailsAsync(process, telemetryContext, "Cassandra", logToFile: true);
+ process.ThrowIfWorkloadFailed();
+ this.CaptureMetrics(process, telemetryContext, argument);
+ }
+
+ if (process.ExitCode != 0)
+ {
+ throw new WorkloadException($"Command failed with exit code {process.ExitCode}.");
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ // Log the error and rethrow
+ this.Logger.LogMessage($"Failed to parse cassandra output: {ex.Message}", LogLevel.Warning, telemetryContext);
+ throw new WorkloadException($"Failed to parse cassandra output: {ex.Message}", ex);
+ }
+ }
+
+ private async Task ExecuteCommandAsync(string pathToExe, string commandLineArguments, string workingDirectory, CancellationToken cancellationToken)
+ {
+ if (!cancellationToken.IsCancellationRequested)
+ {
+ this.Logger.LogTraceMessage($"Executing process '{pathToExe}' '{commandLineArguments}' at directory '{workingDirectory}'.");
+
+ EventContext telemetryContext = EventContext.Persisted()
+ .AddContext("command", pathToExe)
+ .AddContext("commandArguments", commandLineArguments);
+
+ await this.Logger.LogMessageAsync($"{nameof(CassandraExecutor)}.ExecuteProcess", telemetryContext, async () =>
+ {
+ DateTime start = DateTime.Now;
+ using (IProcessProxy process = this.systemManager.ProcessManager.CreateElevatedProcess(this.Platform, pathToExe, commandLineArguments, workingDirectory))
+ {
+ this.CleanupTasks.Add(() => process.SafeKill());
+ await process.StartAndWaitAsync(cancellationToken)
+ .ConfigureAwait(false);
+
+ if (!cancellationToken.IsCancellationRequested)
+ {
+ await this.LogProcessDetailsAsync(process, telemetryContext)
+ .ConfigureAwait(false);
+
+ process.ThrowIfErrored(errorReason: ErrorReason.WorkloadFailed);
+ }
+ }
+ }).ConfigureAwait(false);
+ }
+ }
+
+ private void CaptureMetrics(IProcessProxy process, EventContext telemetryContext, string commandArguments)
+ {
+ process.ThrowIfNull(nameof(process));
+
+ this.MetadataContract.AddForScenario(
+ "cassandra",
+ commandArguments,
+ toolVersion: null);
+
+ this.MetadataContract.Apply(telemetryContext);
+
+ CassandraMetricsParser parser = new CassandraMetricsParser(process.StandardOutput.ToString());
+ IList metrics = parser.Parse();
+
+ this.Logger.LogMetrics(
+ "cassandra",
+ this.Scenario,
+ process.StartTime,
+ process.ExitTime,
+ metrics,
+ null,
+ commandArguments,
+ this.Tags,
+ telemetryContext);
+ }
+ }
+}
+
diff --git a/src/VirtualClient/VirtualClient.Actions/Cassandra/CassandraMetricsParser.cs b/src/VirtualClient/VirtualClient.Actions/Cassandra/CassandraMetricsParser.cs
index 47e85bbd03..41561c7a4b 100644
--- a/src/VirtualClient/VirtualClient.Actions/Cassandra/CassandraMetricsParser.cs
+++ b/src/VirtualClient/VirtualClient.Actions/Cassandra/CassandraMetricsParser.cs
@@ -1,105 +1,103 @@
-namespace VirtualClient.Actions
-{
- using System;
- using System.Collections.Generic;
- using System.Data;
- using System.Text.RegularExpressions;
- using VirtualClient.Common.Contracts;
- using VirtualClient.Contracts;
- using DataTableExtensions = VirtualClient.Contracts.DataTableExtensions;
-
- ///
- /// Parser for Cassandra output document.
- ///
- public class CassandraMetricsParser : MetricsParser
- {
- private static readonly Regex OpRateRegex = new Regex(@"Op rate\s+:\s+([\d,]+) op/s", RegexOptions.Multiline);
- private static readonly Regex PartitionRateRegex = new Regex(@"Partition rate\s+:\s+([\d,]+) pk/s", RegexOptions.Multiline);
- private static readonly Regex RowRateRegex = new Regex(@"Row rate\s+:\s+([\d,]+) row/s", RegexOptions.Multiline);
- private static readonly Regex LatencyMeanRegex = new Regex(@"Latency mean\s+:\s+([\d.]+) ms", RegexOptions.Multiline);
- private static readonly Regex LatencyMaxRegex = new Regex(@"Latency max\s+:\s+([\d.]+) ms", RegexOptions.Multiline);
- private static readonly Regex TotalErrorsRegex = new Regex(@"Total errors\s+:\s+([\d,]+)", RegexOptions.Multiline);
- private static readonly Regex TotalOperationTimeRegex = new Regex(@"Total operation time\s+:\s+(\d{2}:\d{2}:\d{2})", RegexOptions.Multiline);
-
- ///
- /// constructor for .
- ///
- /// Raw text to parse.
- public CassandraMetricsParser(string rawText)
- : base(rawText)
- {
- }
-
- ///
- /// List of parsed metrics from the YCSB output.
- ///
- public List Metrics { get; set; } = new List();
-
- ///
- public override IList Parse()
- {
- this.Preprocess();
- this.ThrowIfInvalidOutputFormat();
- this.ExtractMetrics();
-
- return this.Metrics;
- }
-
- ///
- protected override void Preprocess()
- {
- // Normalize the text to ensure consistent formatting.
- this.PreprocessedText = Regex.Replace(this.RawText, "\r\n", "\n");
- this.PreprocessedText = Regex.Replace(this.PreprocessedText, "\n\n", "\n"); // Consolidate multiple newlines
- }
-
- ///
- private void ThrowIfInvalidOutputFormat()
- {
- if (string.IsNullOrWhiteSpace(this.PreprocessedText))
- {
- throw new SchemaException("The Cassandra Stress output has incorrect format for parsing: empty or null text.");
- }
-
- if (!OpRateRegex.IsMatch(this.PreprocessedText))
- {
- throw new SchemaException("The Cassandra Stress output has incorrect format for parsing: missing key metrics.");
- }
- }
-
- private void ExtractMetrics()
- {
- this.ExtractMetric(OpRateRegex, "Op Rate", "ops/s");
- this.ExtractMetric(PartitionRateRegex, "Partition Rate", "pk/s");
- this.ExtractMetric(RowRateRegex, "Row Rate", "row/s");
- this.ExtractMetric(LatencyMeanRegex, "Latency Mean", "ms");
- this.ExtractMetric(LatencyMaxRegex, "Latency Max", "ms");
- this.ExtractMetric(TotalErrorsRegex, "Total Errors", "count");
- // this.ExtractMetric(TotalOperationTimeRegex, "Total Operation Time", "time");
- var match = TotalOperationTimeRegex.Match(this.PreprocessedText);
- if (match.Success)
- {
- if (TimeSpan.TryParse(match.Groups[1].Value, out TimeSpan operationTime))
- {
- this.Metrics.Add(new Metric("Total Operation Time", operationTime.ToString(@"hh\:mm\:ss"), "hh:mm:ss"));
- }
- else
- {
- throw new FormatException($"Invalid operation time format: {match.Groups[1].Value}");
- }
- }
-
- }
-
- private void ExtractMetric(Regex regex, string metricName, string unit)
- {
- var match = regex.Match(this.PreprocessedText);
- if (match.Success)
- {
- this.Metrics.Add(new Metric(metricName, match.Groups[1].Value, unit));
- }
- }
- }
-}
-
-
+namespace VirtualClient.Actions
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Data;
+ using System.Text.RegularExpressions;
+ using VirtualClient.Common.Contracts;
+ using VirtualClient.Contracts;
+ using DataTableExtensions = VirtualClient.Contracts.DataTableExtensions;
+
+ ///
+ /// Parser for Cassandra output document.
+ ///
+ public class CassandraMetricsParser : MetricsParser
+ {
+ private static readonly Regex OpRateRegex = new Regex(@"Op rate\s+:\s+([\d,]+) op/s", RegexOptions.Multiline);
+ private static readonly Regex PartitionRateRegex = new Regex(@"Partition rate\s+:\s+([\d,]+) pk/s", RegexOptions.Multiline);
+ private static readonly Regex RowRateRegex = new Regex(@"Row rate\s+:\s+([\d,]+) row/s", RegexOptions.Multiline);
+ private static readonly Regex LatencyMeanRegex = new Regex(@"Latency mean\s+:\s+([\d.]+) ms", RegexOptions.Multiline);
+ private static readonly Regex LatencyMaxRegex = new Regex(@"Latency max\s+:\s+([\d.]+) ms", RegexOptions.Multiline);
+ private static readonly Regex TotalErrorsRegex = new Regex(@"Total errors\s+:\s+([\d,]+)", RegexOptions.Multiline);
+ private static readonly Regex TotalOperationTimeRegex = new Regex(@"Total operation time\s+:\s+(\d{2}:\d{2}:\d{2})", RegexOptions.Multiline);
+
+ ///
+ /// constructor for .
+ ///
+ /// Raw text to parse.
+ public CassandraMetricsParser(string rawText)
+ : base(rawText)
+ {
+ }
+
+ ///
+ /// List of parsed metrics from the YCSB output.
+ ///
+ public List Metrics { get; set; } = new List();
+
+ ///
+ public override IList Parse()
+ {
+ this.Preprocess();
+ this.ThrowIfInvalidOutputFormat();
+ this.ExtractMetrics();
+
+ return this.Metrics;
+ }
+
+ ///
+ protected override void Preprocess()
+ {
+ // Normalize the text to ensure consistent formatting.
+ this.PreprocessedText = Regex.Replace(this.RawText, "\r\n", "\n");
+ this.PreprocessedText = Regex.Replace(this.PreprocessedText, "\n\n", "\n"); // Consolidate multiple newlines
+ }
+
+ ///
+ private void ThrowIfInvalidOutputFormat()
+ {
+ if (string.IsNullOrWhiteSpace(this.PreprocessedText))
+ {
+ throw new SchemaException("The Cassandra Stress output has incorrect format for parsing: empty or null text.");
+ }
+
+ if (!OpRateRegex.IsMatch(this.PreprocessedText))
+ {
+ throw new SchemaException("The Cassandra Stress output has incorrect format for parsing: missing key metrics.");
+ }
+ }
+
+ private void ExtractMetrics()
+ {
+ this.ExtractMetric(OpRateRegex, "Op Rate", "ops/s", true);
+ this.ExtractMetric(PartitionRateRegex, "Partition Rate", "pk/s", false);
+ this.ExtractMetric(RowRateRegex, "Row Rate", "row/s", true);
+ this.ExtractMetric(LatencyMeanRegex, "Latency Mean", "ms", false);
+ this.ExtractMetric(LatencyMaxRegex, "Latency Max", "ms", false);
+ this.ExtractMetric(TotalErrorsRegex, "Total Errors", "count", false);
+ var match = TotalOperationTimeRegex.Match(this.PreprocessedText);
+ if (match.Success)
+ {
+ if (TimeSpan.TryParse(match.Groups[1].Value, out TimeSpan operationTime))
+ {
+ double totalSeconds = operationTime.TotalSeconds;
+ this.Metrics.Add(new Metric("Total Operation Time", totalSeconds, "seconds", MetricRelativity.LowerIsBetter));
+ }
+ else
+ {
+ throw new FormatException($"Invalid operation time format: {match.Groups[1].Value}");
+ }
+ }
+ }
+
+ private void ExtractMetric(Regex regex, string metricName, string unit, bool higherIsBetter)
+ {
+ var match = regex.Match(this.PreprocessedText);
+ if (match.Success)
+ {
+ this.Metrics.Add(new Metric(metricName, Convert.ToDouble(match.Groups[1].Value), unit, higherIsBetter ? MetricRelativity.HigherIsBetter : MetricRelativity.LowerIsBetter));
+ }
+ }
+ }
+}
+
diff --git a/src/VirtualClient/VirtualClient.Main/profiles/PERF-CASSANDRA.json b/src/VirtualClient/VirtualClient.Main/profiles/PERF-CASSANDRA.json
index 036b0f10e8..73e5c53085 100644
--- a/src/VirtualClient/VirtualClient.Main/profiles/PERF-CASSANDRA.json
+++ b/src/VirtualClient/VirtualClient.Main/profiles/PERF-CASSANDRA.json
@@ -1,27 +1,28 @@
-{
- "Description": "Cassandra Workload",
- "Metadata": {
- "RecommendedMinimumExecutionTime": "00:05:00",
- "SupportedPlatforms": "linux-x64,linux-arm64,win-x64,win-arm64",
- "SupportedOperatingSystems": "CBL-Mariner,CentOS,Debian,RedHat,Suse,Ubuntu,Windows"
- },
- "Actions": [
- {
- "Type": "CassandraExecutor",
- "Parameters": {
- "Scenario": "Cassandra stress test for READ",
- "command": "cassandra-stress",
- "parameters": "read n=20000 -rate threads=50"
- }
- }
- ],
- "Dependencies": [
- {
- "Type": "LinuxPackageInstallation",
- "Parameters": {
- "Scenario": "InstallCassandraPackage",
- "Packages-Apt": "cassandra"
- }
- }
- ]
-}
+{
+ "Description": "Cassandra Workload",
+ "Metadata": {
+ "RecommendedMinimumExecutionTime": "00:05:00",
+ "SupportedPlatforms": "linux-x64,linux-arm64,win-x64,win-arm64",
+ "SupportedOperatingSystems": "CBL-Mariner,CentOS,Debian,RedHat,Suse,Ubuntu,Windows"
+ },
+ "Actions": [
+ {
+ "Type": "CassandraExecutor",
+ "Parameters": {
+ "Scenario": "Cassandra stress test for READ",
+ "command": "cassandra-stress",
+ "parameters": "read n=20000 -rate threads=50"
+ }
+ }
+ ],
+ "Dependencies": [
+ {
+ "Type": "LinuxPackageInstallation",
+ "Parameters": {
+ "Scenario": "InstallCassandraPackage",
+ "Packages-Apt": "cassandra"
+ }
+ }
+ ]
+}
+
From 44d8d9311b686633a7b29d92ffac8e1fc049fe8f Mon Sep 17 00:00:00 2001
From: Saurabh Saraswat
Date: Mon, 13 Oct 2025 12:35:21 +0000
Subject: [PATCH 4/4] Removed blank lines at the endwq
---
.../VirtualClient.Actions/Cassandra/CassandraExecutor.cs | 1 -
.../VirtualClient.Actions/Cassandra/CassandraMetricsParser.cs | 1 -
2 files changed, 2 deletions(-)
diff --git a/src/VirtualClient/VirtualClient.Actions/Cassandra/CassandraExecutor.cs b/src/VirtualClient/VirtualClient.Actions/Cassandra/CassandraExecutor.cs
index f1118f460e..d3bc0f5734 100644
--- a/src/VirtualClient/VirtualClient.Actions/Cassandra/CassandraExecutor.cs
+++ b/src/VirtualClient/VirtualClient.Actions/Cassandra/CassandraExecutor.cs
@@ -151,4 +151,3 @@ private void CaptureMetrics(IProcessProxy process, EventContext telemetryContext
}
}
}
-
diff --git a/src/VirtualClient/VirtualClient.Actions/Cassandra/CassandraMetricsParser.cs b/src/VirtualClient/VirtualClient.Actions/Cassandra/CassandraMetricsParser.cs
index 41561c7a4b..88e2baec87 100644
--- a/src/VirtualClient/VirtualClient.Actions/Cassandra/CassandraMetricsParser.cs
+++ b/src/VirtualClient/VirtualClient.Actions/Cassandra/CassandraMetricsParser.cs
@@ -100,4 +100,3 @@ private void ExtractMetric(Regex regex, string metricName, string unit, bool hig
}
}
}
-