Add XML docs required by CommentChecker fixes
All checks were successful
NuGet Package Publish / nuget (push) Successful in 1m13s
All checks were successful
NuGet Package Publish / nuget (push) Successful in 1m13s
This commit is contained in:
@@ -3,6 +3,7 @@ using Microsoft.Extensions.Logging;
|
||||
using ZB.MOM.WW.CBDDC.Core.Network;
|
||||
using ZB.MOM.WW.CBDDC.Network;
|
||||
using ZB.MOM.WW.CBDDC.Network.Security;
|
||||
using ZB.MOM.WW.CBDDC.Persistence.Lmdb;
|
||||
using ZB.MOM.WW.CBDDC.Persistence.Surreal;
|
||||
using ZB.MOM.WW.CBDDC.Sample.Console;
|
||||
|
||||
@@ -15,6 +16,18 @@ internal sealed class BenchmarkPeerNode : IAsyncDisposable
|
||||
private readonly string _workDir;
|
||||
private bool _started;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the active EF/Core context for generated users.
|
||||
/// </summary>
|
||||
public SampleDbContext Context { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates and initializes a benchmark peer node.
|
||||
/// </summary>
|
||||
/// <param name="serviceProvider">Service provider containing node dependencies.</param>
|
||||
/// <param name="node">Benchmark node abstraction.</param>
|
||||
/// <param name="context">Live data context used by the benchmark.</param>
|
||||
/// <param name="workDir">Temporary working directory for this node.</param>
|
||||
private BenchmarkPeerNode(
|
||||
ServiceProvider serviceProvider,
|
||||
ICBDDCNode node,
|
||||
@@ -27,8 +40,13 @@ internal sealed class BenchmarkPeerNode : IAsyncDisposable
|
||||
_workDir = workDir;
|
||||
}
|
||||
|
||||
public SampleDbContext Context { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates and starts a benchmark peer node from configuration.
|
||||
/// </summary>
|
||||
/// <param name="nodeId">Unique peer identifier.</param>
|
||||
/// <param name="tcpPort">Local TCP port for the node.</param>
|
||||
/// <param name="authToken">Authentication token shared across peers.</param>
|
||||
/// <param name="knownPeers">Known peers to connect to at startup.</param>
|
||||
public static BenchmarkPeerNode Create(
|
||||
string nodeId,
|
||||
int tcpPort,
|
||||
@@ -58,7 +76,13 @@ internal sealed class BenchmarkPeerNode : IAsyncDisposable
|
||||
services.AddSingleton<ICBDDCSurrealSchemaInitializer, SampleSurrealSchemaInitializer>();
|
||||
services.AddSingleton<SampleDbContext>();
|
||||
|
||||
services.AddCBDDCCore()
|
||||
bool useLmdb = GetBoolEnv("CBDDC_BENCH_USE_LMDB", defaultValue: true);
|
||||
bool dualWrite = GetBoolEnv("CBDDC_BENCH_DUAL_WRITE", defaultValue: true);
|
||||
bool preferLmdbReads = GetBoolEnv("CBDDC_BENCH_PREFER_LMDB_READS", defaultValue: true);
|
||||
bool enableShadowValidation = GetBoolEnv("CBDDC_BENCH_SHADOW_READ_VALIDATE", defaultValue: false);
|
||||
int reconcileIntervalMs = GetIntEnv("CBDDC_BENCH_RECONCILE_INTERVAL_MS", defaultValue: 0);
|
||||
|
||||
var registration = services.AddCBDDCCore()
|
||||
.AddCBDDCSurrealEmbedded<SampleDocumentStore>(_ => new CBDDCSurrealEmbeddedOptions
|
||||
{
|
||||
Endpoint = "rocksdb://local",
|
||||
@@ -72,8 +96,27 @@ internal sealed class BenchmarkPeerNode : IAsyncDisposable
|
||||
PollingInterval = TimeSpan.FromMilliseconds(50),
|
||||
EnableLiveSelectAccelerator = true
|
||||
}
|
||||
})
|
||||
.AddCBDDCNetwork<StaticPeerNodeConfigurationProvider>(false);
|
||||
});
|
||||
|
||||
if (useLmdb)
|
||||
registration.AddCBDDCLmdbOplog(
|
||||
_ => new LmdbOplogOptions
|
||||
{
|
||||
EnvironmentPath = Path.Combine(workDir, "oplog-lmdb"),
|
||||
MapSizeBytes = 256L * 1024 * 1024,
|
||||
MaxDatabases = 16,
|
||||
PruneBatchSize = 512
|
||||
},
|
||||
flags =>
|
||||
{
|
||||
flags.UseLmdbOplog = true;
|
||||
flags.DualWriteOplog = dualWrite;
|
||||
flags.PreferLmdbReads = preferLmdbReads;
|
||||
flags.EnableReadShadowValidation = enableShadowValidation;
|
||||
flags.ReconciliationInterval = TimeSpan.FromMilliseconds(Math.Max(0, reconcileIntervalMs));
|
||||
});
|
||||
|
||||
registration.AddCBDDCNetwork<StaticPeerNodeConfigurationProvider>(false);
|
||||
|
||||
// Benchmark runs use explicit known peers; disable UDP discovery and handshake overhead.
|
||||
services.AddSingleton<IDiscoveryService, PassiveDiscoveryService>();
|
||||
@@ -86,6 +129,9 @@ internal sealed class BenchmarkPeerNode : IAsyncDisposable
|
||||
return new BenchmarkPeerNode(provider, node, context, workDir);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts the node asynchronously.
|
||||
/// </summary>
|
||||
public async Task StartAsync()
|
||||
{
|
||||
if (_started) return;
|
||||
@@ -93,6 +139,9 @@ internal sealed class BenchmarkPeerNode : IAsyncDisposable
|
||||
_started = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the node asynchronously.
|
||||
/// </summary>
|
||||
public async Task StopAsync()
|
||||
{
|
||||
if (!_started) return;
|
||||
@@ -111,22 +160,37 @@ internal sealed class BenchmarkPeerNode : IAsyncDisposable
|
||||
_started = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts or updates a user record.
|
||||
/// </summary>
|
||||
/// <param name="user">User payload.</param>
|
||||
public async Task UpsertUserAsync(User user)
|
||||
{
|
||||
await Context.Users.UpdateAsync(user);
|
||||
await Context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether a user identifier exists.
|
||||
/// </summary>
|
||||
/// <param name="userId">Target user identifier.</param>
|
||||
public bool ContainsUser(string userId)
|
||||
{
|
||||
return Context.Users.Find(u => u.Id == userId).Any();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Counts user identifiers matching the provided prefix.
|
||||
/// </summary>
|
||||
/// <param name="prefix">User identifier prefix.</param>
|
||||
public int CountUsersWithPrefix(string prefix)
|
||||
{
|
||||
return Context.Users.FindAll().Count(u => u.Id.StartsWith(prefix, StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the node and any unmanaged resources.
|
||||
/// </summary>
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
try
|
||||
@@ -156,18 +220,43 @@ internal sealed class BenchmarkPeerNode : IAsyncDisposable
|
||||
}
|
||||
}
|
||||
|
||||
private static bool GetBoolEnv(string key, bool defaultValue)
|
||||
{
|
||||
string? raw = Environment.GetEnvironmentVariable(key);
|
||||
if (string.IsNullOrWhiteSpace(raw)) return defaultValue;
|
||||
if (bool.TryParse(raw, out bool parsed)) return parsed;
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
private static int GetIntEnv(string key, int defaultValue)
|
||||
{
|
||||
string? raw = Environment.GetEnvironmentVariable(key);
|
||||
if (string.IsNullOrWhiteSpace(raw)) return defaultValue;
|
||||
if (int.TryParse(raw, out int parsed)) return parsed;
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
private sealed class PassiveDiscoveryService : IDiscoveryService
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the current list of active peers.
|
||||
/// </summary>
|
||||
public IEnumerable<PeerNode> GetActivePeers()
|
||||
{
|
||||
return Array.Empty<PeerNode>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts discovery.
|
||||
/// </summary>
|
||||
public Task Start()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops discovery.
|
||||
/// </summary>
|
||||
public Task Stop()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
|
||||
@@ -15,6 +15,9 @@ public class E2EThroughputBenchmarks
|
||||
private BenchmarkPeerNode _nodeB = null!;
|
||||
private int _sequence;
|
||||
|
||||
/// <summary>
|
||||
/// Sets up benchmark nodes and prepares the cluster.
|
||||
/// </summary>
|
||||
[GlobalSetup]
|
||||
public async Task GlobalSetupAsync()
|
||||
{
|
||||
@@ -58,6 +61,9 @@ public class E2EThroughputBenchmarks
|
||||
await Task.Delay(500);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles benchmark teardown for the throughput test suite.
|
||||
/// </summary>
|
||||
[GlobalCleanup]
|
||||
public Task GlobalCleanupAsync()
|
||||
{
|
||||
@@ -66,6 +72,9 @@ public class E2EThroughputBenchmarks
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Measures local write throughput against a single node.
|
||||
/// </summary>
|
||||
[Benchmark(Description = "Local write throughput", OperationsPerInvoke = BatchSize)]
|
||||
public async Task LocalWriteThroughput()
|
||||
{
|
||||
@@ -74,6 +83,9 @@ public class E2EThroughputBenchmarks
|
||||
await _nodeA.UpsertUserAsync(CreateUser(userId));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Measures replicated write throughput across two nodes.
|
||||
/// </summary>
|
||||
[Benchmark(Description = "Cross-node replicated throughput", OperationsPerInvoke = BatchSize)]
|
||||
public async Task ReplicatedWriteThroughput()
|
||||
{
|
||||
|
||||
@@ -10,18 +10,24 @@ namespace ZB.MOM.WW.CDBBC.E2E.Benchmark.Tests;
|
||||
[SimpleJob(launchCount: 1, warmupCount: 0, iterationCount: 1)]
|
||||
public class OfflineResyncThroughputBenchmarks
|
||||
{
|
||||
private const int BacklogOperationCount = 10_000;
|
||||
private const int BacklogOperationCount = 100_000;
|
||||
private BenchmarkPeerNode _onlineNode = null!;
|
||||
private BenchmarkPeerNode _offlineNode = null!;
|
||||
private int _runSequence;
|
||||
private string _currentPrefix = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Sets up benchmark resources for offline resync scenarios.
|
||||
/// </summary>
|
||||
[GlobalSetup]
|
||||
public Task GlobalSetupAsync()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles benchmark teardown for offline resync scenarios.
|
||||
/// </summary>
|
||||
[GlobalCleanup]
|
||||
public Task GlobalCleanupAsync()
|
||||
{
|
||||
@@ -30,6 +36,9 @@ public class OfflineResyncThroughputBenchmarks
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prepares write-only workload state for the 100K throughput benchmark.
|
||||
/// </summary>
|
||||
[IterationSetup(Target = nameof(OfflineBacklogWriteThroughput100k))]
|
||||
public void SetupOfflineWriteThroughput()
|
||||
{
|
||||
@@ -37,12 +46,18 @@ public class OfflineResyncThroughputBenchmarks
|
||||
InitializeIterationNodesAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
[Benchmark(Description = "Offline backlog write throughput (10K ops)", OperationsPerInvoke = BacklogOperationCount)]
|
||||
/// <summary>
|
||||
/// Measures offline backlog write throughput for 100K operations.
|
||||
/// </summary>
|
||||
[Benchmark(Description = "Offline backlog write throughput (100K ops)", OperationsPerInvoke = BacklogOperationCount)]
|
||||
public async Task OfflineBacklogWriteThroughput100k()
|
||||
{
|
||||
await WriteBatchAsync(_currentPrefix, BacklogOperationCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prepares nodes and backlog before the re-sync benchmark iteration.
|
||||
/// </summary>
|
||||
[IterationSetup(Target = nameof(OfflineNodeResyncDurationAfter100kBacklog))]
|
||||
public void SetupOfflineResyncBenchmark()
|
||||
{
|
||||
@@ -51,7 +66,10 @@ public class OfflineResyncThroughputBenchmarks
|
||||
WriteBatchAsync(_currentPrefix, BacklogOperationCount).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
[Benchmark(Description = "Offline node re-sync duration after 10K backlog")]
|
||||
/// <summary>
|
||||
/// Measures re-sync duration after processing a 100K-entry offline backlog.
|
||||
/// </summary>
|
||||
[Benchmark(Description = "Offline node re-sync duration after 100K backlog")]
|
||||
public async Task OfflineNodeResyncDurationAfter100kBacklog()
|
||||
{
|
||||
await _offlineNode.StartAsync();
|
||||
|
||||
@@ -57,6 +57,9 @@ public class SurrealLogStorageBenchmarks
|
||||
private string _contextNumericKeyQueryValue = string.Empty;
|
||||
private int _contextNumericValueQueryValue;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the benchmark environment and seeds the dataset.
|
||||
/// </summary>
|
||||
[GlobalSetup]
|
||||
public async Task GlobalSetupAsync()
|
||||
{
|
||||
@@ -97,6 +100,9 @@ public class SurrealLogStorageBenchmarks
|
||||
$"RocksDB size: {sizeBytes / (1024d * 1024d):F2} MiB ({sizeBytes:N0} bytes). Path: {_databasePath}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles benchmark teardown for surreal log storage scenarios.
|
||||
/// </summary>
|
||||
[GlobalCleanup]
|
||||
public Task GlobalCleanupAsync()
|
||||
{
|
||||
@@ -104,6 +110,9 @@ public class SurrealLogStorageBenchmarks
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries the latest log rows by context identifier.
|
||||
/// </summary>
|
||||
[Benchmark(Description = "Query by contextId (latest 200 rows)")]
|
||||
public async Task QueryByContextIdAsync()
|
||||
{
|
||||
@@ -117,6 +126,9 @@ public class SurrealLogStorageBenchmarks
|
||||
new Dictionary<string, object?> { ["contextId"] = _contextIdQueryValue });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries the latest rows by logger and timestamp range.
|
||||
/// </summary>
|
||||
[Benchmark(Description = "Query by loggerName + timestamp range (latest 200 rows)")]
|
||||
public async Task QueryByLoggerAndTimestampAsync()
|
||||
{
|
||||
@@ -137,6 +149,9 @@ public class SurrealLogStorageBenchmarks
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries rows by logger and context key/value pairs.
|
||||
/// </summary>
|
||||
[Benchmark(Description = "Query by loggerName + timestamp + arbitrary context string key/value")]
|
||||
public async Task QueryByLoggerTimestampAndContextKeyAsync()
|
||||
{
|
||||
@@ -169,6 +184,9 @@ public class SurrealLogStorageBenchmarks
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries rows by logger and numeric context key/value pairs.
|
||||
/// </summary>
|
||||
[Benchmark(Description = "Query by loggerName + timestamp + arbitrary context number key/value")]
|
||||
public async Task QueryByLoggerTimestampAndNumericContextKeyAsync()
|
||||
{
|
||||
@@ -201,6 +219,9 @@ public class SurrealLogStorageBenchmarks
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reports RocksDB size for the seeded benchmark database.
|
||||
/// </summary>
|
||||
[Benchmark(Description = "RocksDB size (bytes)")]
|
||||
public long GetDatabaseFileSizeBytes()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user