Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

up c# syntax, make cli defaults in one style #903

Merged
merged 11 commits into from
Feb 7, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
30 changes: 14 additions & 16 deletions benchmarks/csharp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,30 +16,30 @@

public class CommandLineOptions
{
[Option('r', "resultsFile", Required = false, HelpText = "Set the file to which the JSON results are written.")]
public string resultsFile { get; set; } = "../results/csharp-results.json";
[Option('r', "resultsFile", Required = false, HelpText = "Set the file to which the JSON results are written.", Default = "../results/csharp-results.json")]
public string resultsFile { get; set; }

Check warning on line 20 in benchmarks/csharp/Program.cs

View workflow job for this annotation

GitHub Actions / run-tests (6.2.14)

Non-nullable property 'resultsFile' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 20 in benchmarks/csharp/Program.cs

View workflow job for this annotation

GitHub Actions / run-tests (6.2.14)

Non-nullable property 'resultsFile' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 20 in benchmarks/csharp/Program.cs

View workflow job for this annotation

GitHub Actions / run-tests (7.2.3)

Non-nullable property 'resultsFile' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 20 in benchmarks/csharp/Program.cs

View workflow job for this annotation

GitHub Actions / run-tests (7.2.3)

Non-nullable property 'resultsFile' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

[Option('d', "dataSize", Required = false, HelpText = "The size of the sent data in bytes.")]
public int dataSize { get; set; } = 100;
[Option('d', "dataSize", Required = false, HelpText = "The size of the sent data in bytes.", Default = 100)]
public int dataSize { get; set; }

[Option('c', "concurrentTasks", Required = false, HelpText = "The number of concurrent operations to perform.", Default = new[] { 1, 10, 100, 1000 })]
public IEnumerable<int> concurrentTasks { get; set; }

Check warning on line 26 in benchmarks/csharp/Program.cs

View workflow job for this annotation

GitHub Actions / run-tests (6.2.14)

Non-nullable property 'concurrentTasks' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 26 in benchmarks/csharp/Program.cs

View workflow job for this annotation

GitHub Actions / run-tests (6.2.14)

Non-nullable property 'concurrentTasks' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 26 in benchmarks/csharp/Program.cs

View workflow job for this annotation

GitHub Actions / run-tests (7.2.3)

Non-nullable property 'concurrentTasks' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 26 in benchmarks/csharp/Program.cs

View workflow job for this annotation

GitHub Actions / run-tests (7.2.3)

Non-nullable property 'concurrentTasks' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

[Option('l', "clients", Required = false, HelpText = "Which clients should run")]
public string clientsToRun { get; set; } = "all";
[Option('l', "clients", Required = false, HelpText = "Which clients should run", Default = "all")]
public string clientsToRun { get; set; }

Check warning on line 29 in benchmarks/csharp/Program.cs

View workflow job for this annotation

GitHub Actions / run-tests (6.2.14)

Non-nullable property 'clientsToRun' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 29 in benchmarks/csharp/Program.cs

View workflow job for this annotation

GitHub Actions / run-tests (6.2.14)

Non-nullable property 'clientsToRun' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 29 in benchmarks/csharp/Program.cs

View workflow job for this annotation

GitHub Actions / run-tests (7.2.3)

Non-nullable property 'clientsToRun' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 29 in benchmarks/csharp/Program.cs

View workflow job for this annotation

GitHub Actions / run-tests (7.2.3)

Non-nullable property 'clientsToRun' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

[Option('h', "host", Required = false, HelpText = "What host to target")]
public string host { get; set; } = "localhost";
[Option('h', "host", Required = false, HelpText = "What host to target", Default = "localhost")]
public string host { get; set; }

Check warning on line 32 in benchmarks/csharp/Program.cs

View workflow job for this annotation

GitHub Actions / run-tests (6.2.14)

Non-nullable property 'host' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 32 in benchmarks/csharp/Program.cs

View workflow job for this annotation

GitHub Actions / run-tests (6.2.14)

Non-nullable property 'host' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 32 in benchmarks/csharp/Program.cs

View workflow job for this annotation

GitHub Actions / run-tests (7.2.3)

Non-nullable property 'host' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 32 in benchmarks/csharp/Program.cs

View workflow job for this annotation

GitHub Actions / run-tests (7.2.3)

Non-nullable property 'host' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

[Option('C', "clientCount", Required = false, HelpText = "Number of clients to run concurrently", Default = new[] { 1 })]
public IEnumerable<int> clientCount { get; set; }

Check warning on line 35 in benchmarks/csharp/Program.cs

View workflow job for this annotation

GitHub Actions / run-tests (6.2.14)

Non-nullable property 'clientCount' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 35 in benchmarks/csharp/Program.cs

View workflow job for this annotation

GitHub Actions / run-tests (6.2.14)

Non-nullable property 'clientCount' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 35 in benchmarks/csharp/Program.cs

View workflow job for this annotation

GitHub Actions / run-tests (7.2.3)

Non-nullable property 'clientCount' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 35 in benchmarks/csharp/Program.cs

View workflow job for this annotation

GitHub Actions / run-tests (7.2.3)

Non-nullable property 'clientCount' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

[Option('t', "tls", HelpText = "Should benchmark a TLS server")]
public bool tls { get; set; } = false;
[Option('t', "tls", HelpText = "Should benchmark a TLS server", Default = false)]
public bool tls { get; set; }


[Option('m', "minimal", HelpText = "Should use a minimal number of actions")]
public bool minimal { get; set; } = false;
[Option('m', "minimal", HelpText = "Should use a minimal number of actions", Default = false)]
public bool minimal { get; set; }
}

private const int PORT = 6379;
Expand Down Expand Up @@ -118,10 +118,8 @@

private static void print_results(string resultsFile)
{
using (FileStream createStream = File.Create(resultsFile))
{
JsonSerializer.Serialize(createStream, bench_json_results);
}
using FileStream createStream = File.Create(resultsFile);
JsonSerializer.Serialize(createStream, bench_json_results);
}

private static async Task redis_benchmark(
Expand Down
160 changes: 78 additions & 82 deletions csharp/lib/AsyncClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,114 +4,110 @@

using System.Runtime.InteropServices;

namespace Glide
namespace Glide;

public class AsyncClient : IDisposable
{
public class AsyncClient : IDisposable
#region public methods
public AsyncClient(string host, UInt32 port, bool useTLS)
{
#region public methods
public AsyncClient(string host, UInt32 port, bool useTLS)
successCallbackDelegate = SuccessCallback;
var successCallbackPointer = Marshal.GetFunctionPointerForDelegate(successCallbackDelegate);
failureCallbackDelegate = FailureCallback;
var failureCallbackPointer = Marshal.GetFunctionPointerForDelegate(failureCallbackDelegate);
clientPointer = CreateClientFfi(host, port, useTLS, successCallbackPointer, failureCallbackPointer);
if (clientPointer == IntPtr.Zero)
{
successCallbackDelegate = SuccessCallback;
var successCallbackPointer = Marshal.GetFunctionPointerForDelegate(successCallbackDelegate);
failureCallbackDelegate = FailureCallback;
var failureCallbackPointer = Marshal.GetFunctionPointerForDelegate(failureCallbackDelegate);
clientPointer = CreateClientFfi(host, port, useTLS, successCallbackPointer, failureCallbackPointer);
if (clientPointer == IntPtr.Zero)
{
throw new Exception("Failed creating a client");
}
throw new Exception("Failed creating a client");
}
}

public async Task SetAsync(string key, string value)
{
var message = messageContainer.GetMessageForCall(key, value);
SetFfi(clientPointer, (ulong)message.Index, message.KeyPtr, message.ValuePtr);
await message;
}
public async Task SetAsync(string key, string value)
{
var message = messageContainer.GetMessageForCall(key, value);
SetFfi(clientPointer, (ulong)message.Index, message.KeyPtr, message.ValuePtr);
await message;
}

public async Task<string?> GetAsync(string key)
{
var message = messageContainer.GetMessageForCall(key, null);
GetFfi(clientPointer, (ulong)message.Index, message.KeyPtr);
return await message;
}
public async Task<string?> GetAsync(string key)
{
var message = messageContainer.GetMessageForCall(key, null);
GetFfi(clientPointer, (ulong)message.Index, message.KeyPtr);
return await message;
}

public void Dispose()
public void Dispose()
{
if (clientPointer == IntPtr.Zero)
{
if (clientPointer == IntPtr.Zero)
{
return;
}
messageContainer.DisposeWithError(null);
CloseClientFfi(clientPointer);
clientPointer = IntPtr.Zero;
return;
}
messageContainer.DisposeWithError(null);
CloseClientFfi(clientPointer);
clientPointer = IntPtr.Zero;
}

#endregion public methods
#endregion public methods

#region private methods
#region private methods

private void SuccessCallback(ulong index, IntPtr str)
private void SuccessCallback(ulong index, IntPtr str)
{
var result = str == IntPtr.Zero ? null : Marshal.PtrToStringAnsi(str);
// Work needs to be offloaded from the calling thread, because otherwise we might starve the client's thread pool.
Task.Run(() =>
{
var result = str == IntPtr.Zero ? null : Marshal.PtrToStringAnsi(str);
// Work needs to be offloaded from the calling thread, because otherwise we might starve the client's thread pool.
Task.Run(() =>
{
var message = messageContainer.GetMessage((int)index);
message.SetResult(result);
});
}
var message = messageContainer.GetMessage((int)index);
message.SetResult(result);
});
}

private void FailureCallback(ulong index)
private void FailureCallback(ulong index)
{
// Work needs to be offloaded from the calling thread, because otherwise we might starve the client's thread pool.
Task.Run(() =>
{
// Work needs to be offloaded from the calling thread, because otherwise we might starve the client's thread pool.
Task.Run(() =>
{
var message = messageContainer.GetMessage((int)index);
message.SetException(new Exception("Operation failed"));
});
}
var message = messageContainer.GetMessage((int)index);
message.SetException(new Exception("Operation failed"));
});
}

~AsyncClient()
{
Dispose();
}
#endregion private methods
~AsyncClient() => Dispose();
#endregion private methods

#region private fields
#region private fields

/// Held as a measure to prevent the delegate being garbage collected. These are delegated once
/// and held in order to prevent the cost of marshalling on each function call.
private FailureAction failureCallbackDelegate;
/// Held as a measure to prevent the delegate being garbage collected. These are delegated once
/// and held in order to prevent the cost of marshalling on each function call.
private FailureAction failureCallbackDelegate;

/// Held as a measure to prevent the delegate being garbage collected. These are delegated once
/// and held in order to prevent the cost of marshalling on each function call.
private StringAction successCallbackDelegate;
/// Held as a measure to prevent the delegate being garbage collected. These are delegated once
/// and held in order to prevent the cost of marshalling on each function call.
private StringAction successCallbackDelegate;

/// Raw pointer to the underlying native client.
private IntPtr clientPointer;
/// Raw pointer to the underlying native client.
private IntPtr clientPointer;

private readonly MessageContainer<string> messageContainer = new();
private readonly MessageContainer<string> messageContainer = new();

#endregion private fields
#endregion private fields

#region FFI function declarations
#region FFI function declarations

private delegate void StringAction(ulong index, IntPtr str);
private delegate void FailureAction(ulong index);
[DllImport("libglide_rs", CallingConvention = CallingConvention.Cdecl, EntryPoint = "get")]
private static extern void GetFfi(IntPtr client, ulong index, IntPtr key);
private delegate void StringAction(ulong index, IntPtr str);
private delegate void FailureAction(ulong index);
[DllImport("libglide_rs", CallingConvention = CallingConvention.Cdecl, EntryPoint = "get")]
private static extern void GetFfi(IntPtr client, ulong index, IntPtr key);

[DllImport("libglide_rs", CallingConvention = CallingConvention.Cdecl, EntryPoint = "set")]
private static extern void SetFfi(IntPtr client, ulong index, IntPtr key, IntPtr value);
[DllImport("libglide_rs", CallingConvention = CallingConvention.Cdecl, EntryPoint = "set")]
private static extern void SetFfi(IntPtr client, ulong index, IntPtr key, IntPtr value);

private delegate void IntAction(IntPtr arg);
[DllImport("libglide_rs", CallingConvention = CallingConvention.Cdecl, EntryPoint = "create_client")]
private static extern IntPtr CreateClientFfi(String host, UInt32 port, bool useTLS, IntPtr successCallback, IntPtr failureCallback);
private delegate void IntAction(IntPtr arg);
[DllImport("libglide_rs", CallingConvention = CallingConvention.Cdecl, EntryPoint = "create_client")]
private static extern IntPtr CreateClientFfi(String host, UInt32 port, bool useTLS, IntPtr successCallback, IntPtr failureCallback);

[DllImport("libglide_rs", CallingConvention = CallingConvention.Cdecl, EntryPoint = "close_client")]
private static extern void CloseClientFfi(IntPtr client);
[DllImport("libglide_rs", CallingConvention = CallingConvention.Cdecl, EntryPoint = "close_client")]
private static extern void CloseClientFfi(IntPtr client);

#endregion
}
#endregion
}
129 changes: 63 additions & 66 deletions csharp/lib/Logger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,83 +6,80 @@
using System.Text;


namespace Glide
{
// TODO - use a bindings generator to create this enum.
public enum Level
{
Error = 0,
Warn = 1,
Info = 2,
Debug = 3,
Trace = 4
}
namespace Glide;

/*
A class that allows logging which is consistent with logs from the internal rust core.
Only one instance of this class can exist at any given time. The logger can be set up in 2 ways -
1. By calling init, which creates and modifies a new logger only if one doesn't exist.
2. By calling setConfig, which replaces the existing logger, and means that new logs will not be saved with the logs that were sent before the call.
If no call to any of these function is received, the first log attempt will initialize a new logger with default level decided by rust core (normally - console, error).
*/
public class Logger
{
#region private fields
// TODO - use a bindings generator to create this enum.
public enum Level
{
Error = 0,
Warn = 1,
Info = 2,
Debug = 3,
Trace = 4
}

private static Level? loggerLevel = null;
#endregion private fields
/*
A class that allows logging which is consistent with logs from the internal rust core.
Only one instance of this class can exist at any given time. The logger can be set up in 2 ways -
1. By calling init, which creates and modifies a new logger only if one doesn't exist.
2. By calling setConfig, which replaces the existing logger, and means that new logs will not be saved with the logs that were sent before the call.
If no call to any of these function is received, the first log attempt will initialize a new logger with default level decided by rust core (normally - console, error).
*/
public class Logger
{
#region private fields

#region internal methods
// Initialize a logger instance if none were initialized before - this method is meant to be used when there is no intention to replace an existing logger.
// The logger will filter all logs with a level lower than the given level,
// If given a fileName argument, will write the logs to files postfixed with fileName. If fileName isn't provided, the logs will be written to the console.
internal static void Init(Level? level, string? filename = null)
{
if (Logger.loggerLevel is null)
{
SetLoggerConfig(level, filename);
}
}
private static Level? loggerLevel = null;
#endregion private fields

// take the arguments from the user and provide to the core-logger (see ../logger-core)
// if the level is higher then the logger level (error is 0, warn 1, etc.) simply return without operation
// if a logger instance doesn't exist, create new one with default mode (decided by rust core, normally - level: error, target: console)
// logIdentifier arg is a string contain data that suppose to give the log a context and make it easier to find certain type of logs.
// when the log is connect to certain task the identifier should be the task id, when the log is not part of specific task the identifier should give a context to the log - for example, "create client".
internal static void Log(Level logLevel, string logIdentifier, string message)
#region internal methods
// Initialize a logger instance if none were initialized before - this method is meant to be used when there is no intention to replace an existing logger.
// The logger will filter all logs with a level lower than the given level,
// If given a fileName argument, will write the logs to files postfixed with fileName. If fileName isn't provided, the logs will be written to the console.
internal static void Init(Level? level, string? filename = null)
{
if (Logger.loggerLevel is null)
{
if (Logger.loggerLevel is null)
{
SetLoggerConfig(logLevel);
}
if (!(logLevel <= Logger.loggerLevel)) return;
log(Convert.ToInt32(logLevel), Encoding.UTF8.GetBytes(logIdentifier), Encoding.UTF8.GetBytes(message));
SetLoggerConfig(level, filename);
}
#endregion internal methods
}

#region public methods
// config the logger instance - in fact - create new logger instance with the new args
// exist in addition to init for two main reason's:
// 1. if GLIDE dev want intentionally to change the logger instance configuration
// 2. external user want to set the logger and we don't want to return to him the logger itself, just config it
// the level argument is the level of the logs you want the system to provide (error logs, warn logs, etc.)
// the filename argument is optional - if provided the target of the logs will be the file mentioned, else will be the console
public static void SetLoggerConfig(Level? level, string? filename = null)
// take the arguments from the user and provide to the core-logger (see ../logger-core)
// if the level is higher then the logger level (error is 0, warn 1, etc.) simply return without operation
// if a logger instance doesn't exist, create new one with default mode (decided by rust core, normally - level: error, target: console)
// logIdentifier arg is a string contain data that suppose to give the log a context and make it easier to find certain type of logs.
// when the log is connect to certain task the identifier should be the task id, when the log is not part of specific task the identifier should give a context to the log - for example, "create client".
internal static void Log(Level logLevel, string logIdentifier, string message)
{
if (Logger.loggerLevel is null)
{
var buffer = filename is null ? null : Encoding.UTF8.GetBytes(filename);
Logger.loggerLevel = InitInternalLogger(Convert.ToInt32(level), buffer);
SetLoggerConfig(logLevel);
}
#endregion public methods

#region FFI function declaration
[DllImport("libglide_rs", CallingConvention = CallingConvention.Cdecl, EntryPoint = "log")]
private static extern void log(Int32 logLevel, byte[] logIdentifier, byte[] message);

[DllImport("libglide_rs", CallingConvention = CallingConvention.Cdecl, EntryPoint = "init")]
private static extern Level InitInternalLogger(Int32 level, byte[]? filename);
if (!(logLevel <= Logger.loggerLevel)) return;
log(Convert.ToInt32(logLevel), Encoding.UTF8.GetBytes(logIdentifier), Encoding.UTF8.GetBytes(message));
}
#endregion internal methods

#endregion
#region public methods
// config the logger instance - in fact - create new logger instance with the new args
// exist in addition to init for two main reason's:
// 1. if GLIDE dev want intentionally to change the logger instance configuration
// 2. external user want to set the logger and we don't want to return to him the logger itself, just config it
// the level argument is the level of the logs you want the system to provide (error logs, warn logs, etc.)
// the filename argument is optional - if provided the target of the logs will be the file mentioned, else will be the console
public static void SetLoggerConfig(Level? level, string? filename = null)
{
var buffer = filename is null ? null : Encoding.UTF8.GetBytes(filename);
Logger.loggerLevel = InitInternalLogger(Convert.ToInt32(level), buffer);
}
#endregion public methods

#region FFI function declaration
[DllImport("libglide_rs", CallingConvention = CallingConvention.Cdecl, EntryPoint = "log")]
private static extern void log(Int32 logLevel, byte[] logIdentifier, byte[] message);

[DllImport("libglide_rs", CallingConvention = CallingConvention.Cdecl, EntryPoint = "init")]
private static extern Level InitInternalLogger(Int32 level, byte[]? filename);

#endregion
}
Loading
Loading