Skip to content

Recording redis version in the executor #526

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace VirtualClient.Actions
using System.Linq;
using System.Net;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Moq;
Expand Down Expand Up @@ -44,6 +45,24 @@ public void SetupFixture()
[TestCase("PERF-REDIS.json")]
public async Task RedisMemtierWorkloadProfileInstallsTheExpectedDependenciesOfServerOnUnixPlatform(string profile)
{
using var memoryProcess = new InMemoryProcess
{
StandardOutput = new ConcurrentBuffer(
new StringBuilder("Redis server v=7.0.15 sha=00000000 malloc=jemalloc-5.1.0 bits=64 build=abc123")),
OnStart = () => true,
OnHasExited = () => true
};

this.mockFixture.ProcessManager.OnCreateProcess = (command, arguments, workingDir) =>
{
IProcessProxy process = this.mockFixture.CreateProcess(command, arguments, workingDir);
if (arguments?.Contains("redis-server") == true && arguments?.Contains("--version") == true)
{
return memoryProcess;
}

return process;
};
using (ProfileExecutor executor = TestDependencies.CreateProfileExecutor(profile, this.mockFixture.Dependencies))
{
await executor.ExecuteAsync(ProfileTiming.OneIteration(), CancellationToken.None)
Expand Down Expand Up @@ -86,10 +105,21 @@ public async Task RedisMemtierWorkloadProfileExecutesTheWorkloadAsExpectedOfServ
});

await apiClient.CreateStateAsync(nameof(ServerState), state, CancellationToken.None);
using var memoryProcess = new InMemoryProcess
{
StandardOutput = new ConcurrentBuffer(
new StringBuilder("Redis server v=7.0.15 sha=00000000 malloc=jemalloc-5.1.0 bits=64 build=abc123")),
OnStart = () => true,
OnHasExited = () => true
};

this.mockFixture.ProcessManager.OnCreateProcess = (command, arguments, workingDir) =>
{
IProcessProxy process = this.mockFixture.CreateProcess(command, arguments, workingDir);
if (arguments?.Contains("redis-server") == true && arguments?.Contains("--version") == true)
{
return memoryProcess;
}

return process;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ namespace VirtualClient.Actions
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using NUnit.Framework;
using VirtualClient.Actions.Memtier;
using VirtualClient.Common;
using VirtualClient.Common.Telemetry;
using VirtualClient.Contracts;

Expand All @@ -21,13 +24,19 @@ public class RedisServerExecutorTests
{
private MockFixture fixture;
private DependencyPath mockRedisPackage;
private InMemoryProcess memoryProcess;

[SetUp]
public void SetupTests()
{
this.fixture = new MockFixture();
this.fixture.Setup(PlatformID.Unix);

this.memoryProcess = new InMemoryProcess
{
ExitCode = 0,
OnStart = () => true,
OnHasExited = () => true
};
this.mockRedisPackage = new DependencyPath("redis", this.fixture.GetPackagePath("redis"));

this.fixture.Parameters = new Dictionary<string, IConvertible>()
Expand Down Expand Up @@ -62,6 +71,17 @@ public async Task RedisServerExecutorConfirmsTheExpectedPackagesOnInitialization
{
using (var component = new TestRedisServerExecutor(this.fixture.Dependencies, this.fixture.Parameters))
{
this.fixture.ProcessManager.OnCreateProcess = (command, arguments, workingDirectory) =>
{
if (arguments?.Contains("redis-server") == true && arguments?.Contains("--version") == true)
{
this.memoryProcess.StandardOutput = new ConcurrentBuffer(
new StringBuilder("Redis server v=7.0.15 sha=00000000 malloc=jemalloc-5.1.0 bits=64 build=abc123")
);
return this.memoryProcess;
}
return this.memoryProcess;
};
await component.InitializeAsync(EventContext.None, CancellationToken.None);
this.fixture.PackageManager.Verify(mgr => mgr.GetPackageAsync(this.mockRedisPackage.Name, It.IsAny<CancellationToken>()));
}
Expand All @@ -83,6 +103,13 @@ public async Task RedisMemtierServerExecutorExecutesExpectedProcessWhenBindingTo

this.fixture.ProcessManager.OnCreateProcess = (exe, arguments, workingDirectory) =>
{
if (arguments?.Contains("redis-server") == true && arguments?.Contains("--version") == true)
{
this.memoryProcess.StandardOutput = new ConcurrentBuffer(
new StringBuilder("Redis server v=7.0.15 sha=00000000 malloc=jemalloc-5.1.0 bits=64 build=abc123")
);
return this.memoryProcess;
}
expectedCommands.Remove($"{exe} {arguments}");
return this.fixture.Process;
};
Expand Down Expand Up @@ -113,6 +140,13 @@ public async Task RedisMemtierServerExecutorExecutesExpectedProcessWhenBindingTo

this.fixture.ProcessManager.OnCreateProcess = (exe, arguments, workingDirectory) =>
{
if (arguments?.Contains("redis-server") == true && arguments?.Contains("--version") == true)
{
this.memoryProcess.StandardOutput = new ConcurrentBuffer(
new StringBuilder("Redis server v=7.0.15 sha=00000000 malloc=jemalloc-5.1.0 bits=64 build=abc123")
);
return this.memoryProcess;
}
expectedCommands.Remove($"{exe} {arguments}");
return this.fixture.Process;
};
Expand Down Expand Up @@ -140,6 +174,13 @@ public async Task RedisMemtierServerExecutorExecutesExpectedProcessWhenNotBindin

this.fixture.ProcessManager.OnCreateProcess = (exe, arguments, workingDirectory) =>
{
if (arguments?.Contains("redis-server") == true && arguments?.Contains("--version") == true)
{
this.memoryProcess.StandardOutput = new ConcurrentBuffer(
new StringBuilder("Redis server v=7.0.15 sha=00000000 malloc=jemalloc-5.1.0 bits=64 build=abc123")
);
return this.memoryProcess;
}
expectedCommands.Remove($"{exe} {arguments}");
return this.fixture.Process;
};
Expand All @@ -149,6 +190,38 @@ public async Task RedisMemtierServerExecutorExecutesExpectedProcessWhenNotBindin
}
}

[Test]
public async Task RedisServerExecutorCapturesRedisVersionSuccessfully()
{
using (var executor = new TestRedisServerExecutor(this.fixture.Dependencies, this.fixture.Parameters))
{
this.fixture.ProcessManager.OnCreateProcess = (command, arguments, workingDirectory) =>
{
if (arguments?.Contains("redis-server") == true && arguments?.Contains("--version") == true)
{
this.memoryProcess.StandardOutput = new ConcurrentBuffer(
new StringBuilder("Redis server v=7.0.15 sha=00000000 malloc=jemalloc-5.1.0 bits=64 build=abc123")
);
return this.memoryProcess;
}
return this.memoryProcess;
};
// Act
await executor.InitializeAsync(EventContext.None, CancellationToken.None);
// Assert
var messages = this.fixture.Logger.MessagesLogged($"{nameof(TestRedisServerExecutor)}.RedisVersionCaptured");
Assert.IsNotEmpty(messages, "Expected at least one log message indicating the Redis version was captured.");
bool versionCapturedCorrectly = messages.Any(msg =>
{
var eventContext = msg.Item3 as EventContext;
return eventContext != null &&
eventContext.Properties.ContainsKey("redisVersion") &&
eventContext.Properties["redisVersion"].ToString() == "7.0.15";
});
Assert.IsTrue(versionCapturedCorrectly, "The Redis version '7.0.15' was not captured correctly in the logs.");
}
}

private class TestRedisServerExecutor : RedisServerExecutor
{
public TestRedisServerExecutor(IServiceCollection services, IDictionary<string, IConvertible> parameters = null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace VirtualClient.Actions
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
Expand All @@ -19,6 +20,7 @@ namespace VirtualClient.Actions
using VirtualClient.Common.Extensions;
using VirtualClient.Common.Telemetry;
using VirtualClient.Contracts;
using VirtualClient.Contracts.Metadata;
using VirtualClient.Logging;

/// <summary>
Expand Down Expand Up @@ -142,6 +144,8 @@ protected IApiClient ApiClient
/// </summary>
protected IAsyncPolicy ServerRetryPolicy { get; set; }

private string RedisVersion { get; set; }

/// <summary>
/// Disposes of resources used by the executor including shutting down any
/// instances of Redis server running.
Expand Down Expand Up @@ -195,7 +199,6 @@ protected override Task ExecuteAsync(EventContext telemetryContext, Cancellation

await this.SaveStateAsync(telemetryContext, cancellationToken);
this.SetServerOnline(true);

if (this.IsMultiRoleLayout())
{
using (BackgroundOperations profiling = BackgroundOperations.BeginProfiling(this, cancellationToken))
Expand Down Expand Up @@ -237,13 +240,13 @@ protected override async Task InitializeAsync(EventContext telemetryContext, Can
this.RedisExecutablePath = this.Combine(this.RedisPackagePath, "src", "redis-server");

await this.SystemManagement.MakeFileExecutableAsync(this.RedisExecutablePath, this.Platform, cancellationToken);

await this.CaptureRedisVersionAsync(telemetryContext, cancellationToken);
if (this.IsTLSEnabled)
{
DependencyPath redisResourcesPath = await this.GetPackageAsync(this.RedisResourcesPackageName, cancellationToken);
this.RedisResourcesPath = redisResourcesPath.Path;
}

this.InitializeApiClients();
}

Expand All @@ -264,6 +267,32 @@ protected override void Validate()
}
}

private async Task CaptureRedisVersionAsync(EventContext telemetryContext, CancellationToken token)
{
try
{
string command = $"{this.RedisExecutablePath} --version";
IProcessProxy process = await this.ExecuteCommandAsync(command, null, this.RedisPackagePath, telemetryContext, token, runElevated: true);
string output = process.StandardOutput.ToString();
Match match = Regex.Match(output, @"v=(\d+\.\d+\.\d+)");
this.RedisVersion = match.Success ? match.Groups[1].Value : null;
if (!string.IsNullOrEmpty(this.RedisVersion))
{
telemetryContext.AddContext("RedisVersion", this.RedisVersion);
this.Logger.LogMessage($"{this.TypeName}.RedisVersionCaptured", LogLevel.Information, telemetryContext);
this.MetadataContract.AddForScenario(
"Redis-Benchmark",
null,
toolVersion: this.RedisVersion);
this.MetadataContract.Apply(telemetryContext);
}
}
catch (Exception ex)
{
this.Logger.LogErrorMessage(ex, telemetryContext);
}
}

private Task DeleteStateAsync(EventContext telemetryContext, CancellationToken cancellationToken)
{
EventContext relatedContext = telemetryContext.Clone();
Expand Down
Loading