Skip to content

Commit

Permalink
No commit message
Browse files Browse the repository at this point in the history
  • Loading branch information
2 parents 46d0468 + 0022707 commit 5341e33
Show file tree
Hide file tree
Showing 18 changed files with 388 additions and 21 deletions.
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Running tests

Unless you are testing older .NET runtimes on Windows, you should run the tests against the newer runtimes as follows:
- https://dotnet.microsoft.com/en-us/download/dotnet/6.0

```
make test-net6
Expand Down
2 changes: 1 addition & 1 deletion examples/DictionaryExample/DictionaryExample.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="6.0.0" />
<PackageReference Include="Momento.Sdk" Version="1.9.0" />
<PackageReference Include="Momento.Sdk" Version="1.16.0" />
</ItemGroup>
</Project>
2 changes: 1 addition & 1 deletion examples/DocExampleApis/DocExampleApis.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Momento.Sdk" Version="1.13.0" />
<PackageReference Include="Momento.Sdk" Version="1.16.0" />
</ItemGroup>

</Project>
2 changes: 1 addition & 1 deletion examples/MomentoApplication/MomentoApplication.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Momento.Sdk" Version="1.9.0" />
<PackageReference Include="Momento.Sdk" Version="1.16.0" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
Expand All @@ -10,6 +10,6 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Momento.Sdk" Version="1.9.0" />
<PackageReference Include="Momento.Sdk" Version="1.16.0" />
</ItemGroup>
</Project>
2 changes: 1 addition & 1 deletion examples/MomentoLoadGen/MomentoLoadGen.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,6 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Momento.Sdk" Version="1.9.0" />
<PackageReference Include="Momento.Sdk" Version="1.16.0" />
</ItemGroup>
</Project>
31 changes: 21 additions & 10 deletions examples/MomentoLoadGen/Program.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
using System;
using System.Diagnostics;
using System.Diagnostics.Metrics;
using System.Security.Cryptography;
using System.Text;
using Grpc.Core;
using System.Diagnostics;
using HdrHistogram;
using Microsoft.Extensions.Logging;
using Momento.Sdk;
Expand All @@ -30,7 +25,9 @@ enum AsyncSetGetResult
UNAVAILABLE,
TIMEOUT,
LIMIT_EXCEEDED,
RST_STREAM
RST_STREAM,
UNKNOWN,
CANCELLED
};

internal class CsharpLoadGeneratorContext
Expand All @@ -45,6 +42,8 @@ internal class CsharpLoadGeneratorContext
public int GlobalUnavailableCount;
public int GlobalTimeoutExceededCount;
public int GlobalLimitExceededCount;
public int GlobalUnknownCount;
public int GlobalCancelledCount;
public int GlobalRstStreamCount;

public CsharpLoadGeneratorContext()
Expand All @@ -58,6 +57,8 @@ public CsharpLoadGeneratorContext()
GlobalSuccessCount = 0;
GlobalTimeoutExceededCount = 0;
GlobalLimitExceededCount = 0;
GlobalUnknownCount = 0;
GlobalCancelledCount = 0;
GlobalRstStreamCount = 0;
GlobalUnavailableCount = 0;
}
Expand All @@ -67,8 +68,7 @@ public CsharpLoadGeneratorContext()
public class CsharpLoadGenerator
{
const int CACHE_ITEM_TTL_SECONDS = 60;
const string CACHE_NAME = "momento-loadgen";
const int NUM_REQUESTS_PER_OPERATION = 2;
const string CACHE_NAME = "dotnet-momento-loadgen";

private readonly ILoggerFactory _loggerFactory;
private readonly ILogger<CsharpLoadGenerator> _logger;
Expand Down Expand Up @@ -189,6 +189,8 @@ private void PrintStats(LongHistogram setsAccumulatingHistogram, LongHistogram g
unavailable: {context.GlobalUnavailableCount} ({PercentRequests(context, context.GlobalUnavailableCount)}%)
timeout exceeded: {context.GlobalTimeoutExceededCount} ({PercentRequests(context, context.GlobalTimeoutExceededCount)}%)
limit exceeded: {context.GlobalLimitExceededCount} ({PercentRequests(context, context.GlobalLimitExceededCount)}%)
cancelled: {context.GlobalCancelledCount} ({PercentRequests(context, context.GlobalCancelledCount)}%)
unknown: {context.GlobalUnknownCount} ({PercentRequests(context, context.GlobalUnknownCount)}%)
rst stream: {context.GlobalRstStreamCount} ({PercentRequests(context, context.GlobalRstStreamCount)}%)
cumulative set latencies:
Expand Down Expand Up @@ -306,9 +308,16 @@ private AsyncSetGetResult ConvertErrorToAsyncSetGetResult(MomentoErrorCode error
{
return AsyncSetGetResult.LIMIT_EXCEEDED;
}
else if (errorCode is MomentoErrorCode.UNKNOWN_ERROR or MomentoErrorCode.UNKNOWN_SERVICE_ERROR)
{
return AsyncSetGetResult.UNKNOWN;
}
else if (errorCode == MomentoErrorCode.CANCELLED_ERROR)
{
return AsyncSetGetResult.CANCELLED;
}
_logger.LogError("UNCAUGHT EXCEPTION: {}", ex);
throw new ApplicationException($"Unsupported error code: {errorCode}");

}

private static void UpdateContextCountsForRequest(
Expand All @@ -324,6 +333,8 @@ AsyncSetGetResult result
AsyncSetGetResult.TIMEOUT => Interlocked.Increment(ref context.GlobalTimeoutExceededCount),
AsyncSetGetResult.LIMIT_EXCEEDED => Interlocked.Increment(ref context.GlobalLimitExceededCount),
AsyncSetGetResult.RST_STREAM => Interlocked.Increment(ref context.GlobalRstStreamCount),
AsyncSetGetResult.UNKNOWN => Interlocked.Increment(ref context.GlobalUnknownCount),
AsyncSetGetResult.CANCELLED => Interlocked.Increment(ref context.GlobalCancelledCount),
_ => throw new Exception($"Unrecognized result: {result}"),
};
return;
Expand Down
8 changes: 8 additions & 0 deletions examples/MomentoLoadGen/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,11 @@ Within the `MomentoLoadGen` directory you can run:
# Run example load generator
MOMENTO_AUTH_TOKEN=<YOUR AUTH TOKEN> dotnet run
```

If you make modifications to the code, remember to do a clean otherwise
the program might not run.

```bash
dotnet clean
MOMENTO_AUTH_TOKEN=<YOUR AUTH TOKEN> dotnet run
```
2 changes: 1 addition & 1 deletion examples/MomentoUsage/MomentoUsage.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Momento.Sdk" Version="1.9.0" />
<PackageReference Include="Momento.Sdk" Version="1.16.0" />
</ItemGroup>
</Project>
42 changes: 41 additions & 1 deletion src/Momento.Sdk/CacheClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Microsoft.Extensions.Logging;
using Momento.Sdk.Auth;
using Momento.Sdk.Config;
using Momento.Sdk.Config.Transport;
using Momento.Sdk.Exceptions;
using Momento.Sdk.Internal;
using Momento.Sdk.Internal.ExtensionMethods;
Expand Down Expand Up @@ -52,7 +53,46 @@ public CacheClient(IConfiguration config, ICredentialProvider authProvider, Time
Utils.ArgumentStrictlyPositive(defaultTtl, "defaultTtl");
this.controlClient = new(_loggerFactory, authProvider.AuthToken, authProvider.ControlEndpoint);
this.dataClients = new List<ScsDataClient>();
for (var i = 1; i <= config.TransportStrategy.GrpcConfig.MinNumGrpcChannels; i++)
int minNumGrpcChannels = this.config.TransportStrategy.GrpcConfig.MinNumGrpcChannels;
int currentMaxConcurrentRequests = this.config.TransportStrategy.MaxConcurrentRequests;
/**

Check warning on line 58 in src/Momento.Sdk/CacheClient.cs

View workflow job for this annotation

GitHub Actions / publish

XML comment is not placed on a valid language element

Check warning on line 58 in src/Momento.Sdk/CacheClient.cs

View workflow job for this annotation

GitHub Actions / publish

XML comment is not placed on a valid language element

Check warning on line 58 in src/Momento.Sdk/CacheClient.cs

View workflow job for this annotation

GitHub Actions / publish

XML comment is not placed on a valid language element

Check warning on line 58 in src/Momento.Sdk/CacheClient.cs

View workflow job for this annotation

GitHub Actions / publish

XML comment is not placed on a valid language element

Check warning on line 58 in src/Momento.Sdk/CacheClient.cs

View workflow job for this annotation

GitHub Actions / publish

XML comment is not placed on a valid language element

Check warning on line 58 in src/Momento.Sdk/CacheClient.cs

View workflow job for this annotation

GitHub Actions / publish

XML comment is not placed on a valid language element

Check warning on line 58 in src/Momento.Sdk/CacheClient.cs

View workflow job for this annotation

GitHub Actions / publish

XML comment is not placed on a valid language element

Check warning on line 58 in src/Momento.Sdk/CacheClient.cs

View workflow job for this annotation

GitHub Actions / publish

XML comment is not placed on a valid language element

Check warning on line 58 in src/Momento.Sdk/CacheClient.cs

View workflow job for this annotation

GitHub Actions / publish

XML comment is not placed on a valid language element

Check warning on line 58 in src/Momento.Sdk/CacheClient.cs

View workflow job for this annotation

GitHub Actions / publish

XML comment is not placed on a valid language element
* Client Configuration Logic:
*
* At the time of writing, customers have two client configurations affecting the number of gRPC connections spawned:
*
* 1. MinNumGrpcChannels: Determines the number of unique data clients to create based on the provided value.
* Each client eagerly creates one unique connection.
*
* 2. MaxConcurrentRequests: Configures each channel or data client to create a unique connection dynamically/lazily
* when 100 client concurrent requests are hit.
*
* For example, if we have 2 channels and a client provides a value of 200 for MaxConcurrentRequests, we can create a
* maximum of 2 * (200 / 100) ≈ 4 unique connections.
*
* Understanding Client Expectations:
*
* The client presumes that MaxConcurrentRequests is applied at a global level rather than per channel or data client.
* While some clients might utilize minNumGrpcChannels, it is expected that such clients are few and not the majority.
*
* Logic Implementation:
*
* This logic ensures that we honor the maxConcurrentRequests provided by a client if they also provide minNumGrpcChannels,
* and we internally "distribute" the max concurrent requests evenly over all the channels. This makes sure that
* we honor client's maxConcurrentRequests at a global level, and also create the number of channels they request.
* If they do not explicitly provide the number of channels, we default to 1 with the max concurrent requests
* applied to that single channel.
*/
if (minNumGrpcChannels > 1)
{
int newMaxConcurrentRequests = Math.Max(1, currentMaxConcurrentRequests / minNumGrpcChannels);
ITransportStrategy transportStrategy = this.config.TransportStrategy
.WithMaxConcurrentRequests(newMaxConcurrentRequests);
this.config = this.config.WithTransportStrategy(transportStrategy);
_logger.LogWarning("Overriding maxConcurrentRequests for each gRPC channel to {}." +
" Min gRPC channels: {}, total maxConcurrentRequests: {}", newMaxConcurrentRequests,
minNumGrpcChannels, currentMaxConcurrentRequests);
}
for (var i = 1; i <= minNumGrpcChannels; i++)
{
this.dataClients.Add(new(config, authProvider.AuthToken, authProvider.CacheEndpoint, defaultTtl));
}
Expand Down
4 changes: 2 additions & 2 deletions src/Momento.Sdk/Config/Configurations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public static IConfiguration V1(ILoggerFactory? loggerFactory = null)
IRetryStrategy retryStrategy = new FixedCountRetryStrategy(finalLoggerFactory, maxAttempts: 3);
ITransportStrategy transportStrategy = new StaticTransportStrategy(
loggerFactory: finalLoggerFactory,
maxConcurrentRequests: 100,
maxConcurrentRequests: 200, // max of 2 connections https://github.com/momentohq/client-sdk-dotnet/issues/460
grpcConfig: new StaticGrpcConfiguration(deadline: TimeSpan.FromMilliseconds(15000))
);
return new Laptop(finalLoggerFactory, retryStrategy, transportStrategy);
Expand Down Expand Up @@ -107,7 +107,7 @@ public static IConfiguration V1(ILoggerFactory? loggerFactory = null)
IRetryStrategy retryStrategy = new FixedCountRetryStrategy(finalLoggerFactory, maxAttempts: 3);
ITransportStrategy transportStrategy = new StaticTransportStrategy(
loggerFactory: finalLoggerFactory,
maxConcurrentRequests: 200,
maxConcurrentRequests: 400, // max of 4 connections https://github.com/momentohq/client-sdk-dotnet/issues/460
grpcConfig: new StaticGrpcConfiguration(deadline: TimeSpan.FromMilliseconds(1100)));
return new Default(finalLoggerFactory, retryStrategy, transportStrategy);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using System.IO;
using System.Text;

namespace Momento.Sdk.Config.Middleware
{
/// <summary>
/// This middleware enables per-request client-side metrics. Metrics for each
/// request will be written to a CSV file. This file can be analyzed or shared
/// with Momento to diagnose performance issues.
///
/// The metrics format is currently considered experimental. In a future release,
/// once the format is considered stable, this class will be renamed to remove
/// the Experimental prefix.
///
/// WARNING: enabling this middleware may have minor performance implications,
/// so enable with caution.
///
/// WARNING: depending on your request volume, the CSV file size may grow quickly.
/// Neither sampling nor file compression / rotation are included at this time
/// (though they may be added in the future).
/// </summary>
public class ExperimentalMetricsCsvMiddleware : ExperimentalMetricsMiddleware, IDisposable
{
private readonly StreamWriter _writer;
private readonly object _lock = new object();

/// <summary>
/// Constructor for the ExperimentalMetricsCsvMiddleware class.
/// If the file at the specified path exists, the StreamWriter will append to it.
/// If the file does not exist, the StreamWriter will create it.
/// </summary>
/// <param name="filePath">The path to the file where the middleware will write metrics data.</param>
/// <param name="loggerFactory">Used for logging in case of errors.</param>
public ExperimentalMetricsCsvMiddleware(string filePath, ILoggerFactory loggerFactory)
: base(loggerFactory)
{
_writer = new StreamWriter(filePath, true, Encoding.UTF8);
}

/// <summary>
/// Writes metrics for a request out to a CSV.
/// </summary>
public override Task EmitMetrics(ExperimentalRequestMetrics metrics)
{
var csvLine = $"{metrics.NumActiveRequestsAtStart}, {metrics.NumActiveRequestsAtFinish}, " +
$"{metrics.RequestType}, {metrics.Status}, {metrics.StartTime}, {metrics.EndTime}," +
$"{metrics.Duration}, {metrics.RequestSize}, {metrics.ResponseSize}";

lock (_lock)
{
_writer.WriteLine(csvLine);
_writer.Flush();
}

return Task.CompletedTask;
}

/// <summary>
/// Closes the CSV file writer.
/// </summary>
public void Dispose()
{
_writer.Close();
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;

namespace Momento.Sdk.Config.Middleware
{
/// <summary>
/// This middleware enables per-request client-side metrics. Metrics for each
/// request will be written to logs. The log data can be analyzed or shared
/// with Momento to diagnose performance issues.
///
/// The metrics format is currently considered experimental. In a future release,
/// once the format is considered stable, this class will be renamed to remove
/// the Experimental prefix.
///
/// WARNING: enabling this middleware may have minor performance implications,
/// so enable with caution.
///
/// WARNING: depending on your request volume, this middleware will produce a high
/// volume of log output. If you are writing logs directly to local disk, be aware
/// of disk usage and make sure you have log rotation / compression enabled via a
/// tool such as `logrotate`.
/// </summary>
public class ExperimentalMetricsLoggingMiddleware : ExperimentalMetricsMiddleware
{
private readonly ILogger _logger;

/// <summary>
/// Constructor for the ExperimentalMetricsLoggingMiddleware class.
/// </summary>
/// <param name="loggerFactory">Used for logging the metrics and any errors that occur.</param>
public ExperimentalMetricsLoggingMiddleware(ILoggerFactory loggerFactory) : base(loggerFactory)
{
_logger = loggerFactory.CreateLogger<ExperimentalMetricsLoggingMiddleware>();
}

/// <summary>
/// Logs metrics for a Momento request.
/// </summary>
public override Task EmitMetrics(ExperimentalRequestMetrics metrics)
{
var json = "{" +
$"\"numActiveRequestsAtStart\": {metrics.NumActiveRequestsAtStart}, " +
$"\"numActiveRequestsAtFinish\": {metrics.NumActiveRequestsAtFinish}, " +
$"\"requestType\": \"{metrics.RequestType}\", " +
$"\"status\": \"{metrics.Status}\", " +
$"\"startTime\": \"{metrics.StartTime}\", " +
$"\"endTime\": \"{metrics.EndTime}\", " +
$"\"duration\": \"{metrics.Duration}\", " +
$"\"requestSize\": {metrics.RequestSize}, " +
$"\"responseSize\": {metrics.ResponseSize}" +
"}";

_logger.LogInformation(json);

return Task.CompletedTask;
}
}
}
Loading

0 comments on commit 5341e33

Please sign in to comment.