-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore: improvements to load generator
This makes the following improvements to the load generator: * Handles throttles without crashing * Handles timeouts without crashing * Adds a very simplistic implementation of a "max requests per second" setting, which can be used to rate-limit the load generator on the client side
- Loading branch information
Showing
1 changed file
with
83 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,9 +13,11 @@ namespace MomentoLoadGen | |
{ | ||
public record CsharpLoadGeneratorOptions | ||
( | ||
int printStatsEveryNRequests, | ||
uint requestTimeoutMs, | ||
int cacheItemPayloadBytes, | ||
int numberOfConcurrentRequests, | ||
int maxRequestsPerSecond, | ||
int totalNumberOfOperationsToExecute | ||
); | ||
|
||
|
@@ -35,6 +37,7 @@ internal class CsharpLoadGeneratorContext | |
public Recorder SetLatencies; | ||
|
||
public int GlobalRequestCount; | ||
public int LastWorkerStatsPrintRequestCount; | ||
public int GlobalSuccessCount; | ||
public int GlobalUnavailableCount; | ||
public int GlobalDeadlineExceededCount; | ||
|
@@ -48,6 +51,7 @@ public CsharpLoadGeneratorContext() | |
SetLatencies = HistogramFactory.With64BitBucketSize().WithValuesFrom(1).WithValuesUpTo(TimeStamp.Minutes(1)).WithPrecisionOf(1).WithThreadSafeWrites().WithThreadSafeReads().Create(); | ||
|
||
GlobalRequestCount = 0; | ||
LastWorkerStatsPrintRequestCount = 0; | ||
GlobalSuccessCount = 0; | ||
GlobalDeadlineExceededCount = 0; | ||
GlobalResourceExhaustedCount = 0; | ||
|
@@ -62,7 +66,6 @@ public class CsharpLoadGenerator | |
const int CACHE_ITEM_TTL_SECONDS = 60; | ||
const string CACHE_NAME = "momento-loadgen"; | ||
const int NUM_REQUESTS_PER_OPERATION = 2; | ||
const int PRINT_STATS_EVERY_N_REQUESTS = 5000; | ||
|
||
private readonly ILogger<CsharpLoadGenerator> _logger; | ||
private readonly CsharpLoadGeneratorOptions _options; | ||
|
@@ -103,6 +106,8 @@ public async Task Run() | |
|
||
|
||
var numOperationsPerWorker = _options.totalNumberOfOperationsToExecute / _options.numberOfConcurrentRequests; | ||
var workerDelayBetweenRequests = Convert.ToInt32(Math.Floor((1000.0 * _options.numberOfConcurrentRequests) / (_options.maxRequestsPerSecond * 1))); | ||
Console.WriteLine($"Targeting a max of {_options.maxRequestsPerSecond} requests per second (delay between requests: {workerDelayBetweenRequests})"); | ||
var totalNumRequestsExpected = _options.totalNumberOfOperationsToExecute * NUM_REQUESTS_PER_OPERATION; | ||
|
||
var context = new CsharpLoadGeneratorContext(); | ||
|
@@ -113,12 +118,18 @@ public async Task Run() | |
momento, | ||
context, | ||
workerId + 1, | ||
numOperationsPerWorker | ||
numOperationsPerWorker, | ||
workerDelayBetweenRequests, | ||
_options.printStatsEveryNRequests | ||
) | ||
); | ||
).ToList(); | ||
|
||
var statsPrinterTask = LaunchStatsPrinterTask(context, totalNumRequestsExpected); | ||
var statsPrinterTask = LaunchStatsPrinterTask(context, _options.printStatsEveryNRequests, totalNumRequestsExpected); | ||
|
||
var firstResult = await Task.WhenAny(asyncResults); | ||
// this will ensure that the program exits promptly if one of the async | ||
// tasks throws an uncaught exception. | ||
await firstResult; | ||
await Task.WhenAll(asyncResults); | ||
|
||
await statsPrinterTask; | ||
|
@@ -130,26 +141,28 @@ private async Task LaunchAndRunWorkers( | |
SimpleCacheClient client, | ||
CsharpLoadGeneratorContext context, | ||
int workerId, | ||
int numOperations) | ||
int numOperations, | ||
int delayMillisBetweenRequests, | ||
int printStatsEveryNRequests | ||
) | ||
{ | ||
for (var i = 1; i <= numOperations; i++) | ||
{ | ||
await IssueAsyncSetGet(client, context, workerId, i); | ||
|
||
await IssueAsyncSetGet(client, context, workerId, i, delayMillisBetweenRequests, printStatsEveryNRequests); | ||
} | ||
} | ||
|
||
private async Task LaunchStatsPrinterTask(CsharpLoadGeneratorContext context, int totalNumRequests) | ||
private async Task LaunchStatsPrinterTask(CsharpLoadGeneratorContext context, int printStatsEveryNRequests, int totalNumRequests) | ||
{ | ||
var setsAccumulatingHistogram = new LongHistogram(TimeStamp.Minutes(1), 1); | ||
var getsAccumulatingHistogram = new LongHistogram(TimeStamp.Minutes(1), 1); | ||
|
||
var nextStatsUpdateRequestCount = PRINT_STATS_EVERY_N_REQUESTS; | ||
var nextStatsUpdateRequestCount = printStatsEveryNRequests; | ||
while (context.GlobalRequestCount < totalNumRequests) | ||
{ | ||
if (context.GlobalRequestCount >= nextStatsUpdateRequestCount) | ||
{ | ||
nextStatsUpdateRequestCount += PRINT_STATS_EVERY_N_REQUESTS; | ||
nextStatsUpdateRequestCount += printStatsEveryNRequests; | ||
PrintStats(setsAccumulatingHistogram, getsAccumulatingHistogram, context); | ||
} | ||
|
||
|
@@ -183,18 +196,23 @@ private void PrintStats(LongHistogram setsAccumulatingHistogram, LongHistogram g | |
"); | ||
} | ||
|
||
private async Task IssueAsyncSetGet(SimpleCacheClient client, CsharpLoadGeneratorContext context, int workerId, int operationId) | ||
private async Task IssueAsyncSetGet(SimpleCacheClient client, CsharpLoadGeneratorContext context, int workerId, int operationId, int delayMillisBetweenRequests, int printStatsEveryNRequests) | ||
{ | ||
var cacheKey = $"worker{workerId}operation{operationId}"; | ||
|
||
var setStartTime = System.Diagnostics.Stopwatch.StartNew(); | ||
var result = await ExecuteRequestAndUpdateContextCounts( | ||
context, | ||
() => client.SetAsync(CACHE_NAME, cacheKey, _cacheValue) | ||
); | ||
if (result != null) | ||
{ | ||
var setDuration = setStartTime.ElapsedMilliseconds; | ||
var setDuration = setStartTime.ElapsedMilliseconds; | ||
context.SetLatencies.RecordValue(setDuration); | ||
if (setDuration < delayMillisBetweenRequests) | ||
{ | ||
await Task.Delay((int)(delayMillisBetweenRequests - setDuration)); | ||
} | ||
} | ||
|
||
var getStartTime = System.Diagnostics.Stopwatch.StartNew(); | ||
|
@@ -207,6 +225,10 @@ private async Task IssueAsyncSetGet(SimpleCacheClient client, CsharpLoadGenerato | |
{ | ||
var getDuration = getStartTime.ElapsedMilliseconds; | ||
context.GetLatencies.RecordValue(getDuration); | ||
if (getDuration < delayMillisBetweenRequests) | ||
{ | ||
await Task.Delay((int)(delayMillisBetweenRequests - getDuration)); | ||
} | ||
|
||
string valueString; | ||
|
||
|
@@ -220,9 +242,14 @@ private async Task IssueAsyncSetGet(SimpleCacheClient client, CsharpLoadGenerato | |
valueString = "n/a"; | ||
} | ||
|
||
if (context.GlobalRequestCount % PRINT_STATS_EVERY_N_REQUESTS == 0) | ||
var globalRequestCount = context.GlobalRequestCount; | ||
if (globalRequestCount % printStatsEveryNRequests == 0) | ||
{ | ||
_logger.LogInformation($"worker: {workerId}, worker request: {operationId}, global request: {context.GlobalRequestCount}, status: ${getResult.Status}, val: ${valueString}"); | ||
var lastPrintCount = Interlocked.Exchange(ref context.LastWorkerStatsPrintRequestCount, globalRequestCount); | ||
if (lastPrintCount != globalRequestCount) { | ||
Console.WriteLine($"worker: {workerId} last print count: {lastPrintCount} global request count: {globalRequestCount}"); | ||
_logger.LogInformation($"worker: {workerId}, worker request: {operationId}, global request: {context.GlobalRequestCount}, status: {getResult.Status}, val: {valueString}"); | ||
} | ||
} | ||
} | ||
} | ||
|
@@ -260,6 +287,14 @@ Func<Task<T>> block | |
throw e; | ||
} | ||
} | ||
catch (Momento.Sdk.Exceptions.TimeoutException) | ||
{ | ||
return Tuple.Create(AsyncSetGetResult.DEADLINE_EXCEEDED, default(T)); | ||
} | ||
catch (LimitExceededException) | ||
{ | ||
return Tuple.Create(AsyncSetGetResult.RESOURCE_EXHAUSTED, default(T)); | ||
} | ||
catch (Exception e) | ||
{ | ||
_logger.LogError("CAUGHT AN EXCEPTION WHILE EXECUTING REQUEST: {0}", e); | ||
|
@@ -348,31 +383,43 @@ static async Task Main(string[] args) | |
using ILoggerFactory loggerFactory = InitializeLogging(); | ||
|
||
CsharpLoadGeneratorOptions loadGeneratorOptions = new CsharpLoadGeneratorOptions( | ||
/** | ||
* Configures the Momento client to timeout if a request exceeds this limit. | ||
* Momento client default is 5 seconds. | ||
*/ | ||
/// | ||
/// Each time the load generator has executed this many requests, it will | ||
/// print out some statistics about throughput and latency. | ||
/// | ||
printStatsEveryNRequests: 1000, | ||
/// | ||
/// Configures the Momento client to timeout if a request exceeds this limit. | ||
/// Momento client default is 5 seconds. | ||
/// | ||
requestTimeoutMs: 5 * 1000, | ||
/** | ||
* Controls the size of the payload that will be used for the cache items in | ||
* the load test. Smaller payloads will generally provide lower latencies than | ||
* larger payloads. | ||
*/ | ||
/// | ||
/// Controls the size of the payload that will be used for the cache items in | ||
/// the load test. Smaller payloads will generally provide lower latencies than | ||
/// larger payloads. | ||
/// | ||
cacheItemPayloadBytes: 100, | ||
/** | ||
* Controls the number of concurrent requests that will be made (via asynchronous | ||
* function calls) by the load test. Increasing this number may improve throughput, | ||
* but it will also increase CPU consumption. As CPU usage increases and there | ||
* is more contention between the concurrent function calls, client-side latencies | ||
* may increase. | ||
*/ | ||
/// | ||
/// Controls the number of concurrent requests that will be made (via asynchronous | ||
/// function calls) by the load test. Increasing this number may improve throughput, | ||
/// but it will also increase CPU consumption. As CPU usage increases and there | ||
/// is more contention between the concurrent function calls, client-side latencies | ||
/// may increase. | ||
/// | ||
numberOfConcurrentRequests: 50, | ||
/** | ||
* Controls how long the load test will run. We will execute this many operations | ||
* (1 cache 'set' followed immediately by 1 'get') across all of our concurrent | ||
* workers before exiting. Statistics will be logged every 1000 operations. | ||
*/ | ||
totalNumberOfOperationsToExecute: 50_000 | ||
/// | ||
/// Sets an upper bound on how many requests per second will be sent to the server. | ||
/// Momento caches have a default throttling limit of 100 requests per second, | ||
/// so if you raise this, you may observe throttled requests. Contact | ||
/// [email protected] to inquire about raising your limits. | ||
/// | ||
maxRequestsPerSecond: 100, | ||
/// | ||
/// Controls how long the load test will run. We will execute this many operations | ||
/// (1 cache 'set' followed immediately by 1 'get') across all of our concurrent | ||
/// workers before exiting. Statistics will be logged every 1000 operations. | ||
/// | ||
totalNumberOfOperationsToExecute: 500_000 | ||
); | ||
|
||
CsharpLoadGenerator loadGenerator = new CsharpLoadGenerator( | ||
|