using System.Diagnostics; using EphemeralMongo; using MongoDB.Bson; using MongoDB.Driver; namespace StellaOps.Bench.LinkNotMerge.Vex; internal sealed class VexScenarioRunner { private readonly VexScenarioConfig _config; private readonly IReadOnlyList _seeds; public VexScenarioRunner(VexScenarioConfig config) { _config = config ?? throw new ArgumentNullException(nameof(config)); _seeds = VexObservationGenerator.Generate(config); } public VexScenarioExecutionResult Execute(int iterations, CancellationToken cancellationToken) { if (iterations <= 0) { throw new ArgumentOutOfRangeException(nameof(iterations), iterations, "Iterations must be positive."); } var totalDurations = new double[iterations]; var insertDurations = new double[iterations]; var correlationDurations = new double[iterations]; var allocated = new double[iterations]; var observationThroughputs = new double[iterations]; var eventThroughputs = new double[iterations]; VexAggregationResult lastAggregation = new(0, 0, 0, Array.Empty()); for (var iteration = 0; iteration < iterations; iteration++) { cancellationToken.ThrowIfCancellationRequested(); using var runner = MongoRunner.Run(new MongoRunnerOptions { UseSingleNodeReplicaSet = false, }); var client = new MongoClient(runner.ConnectionString); var database = client.GetDatabase("linknotmerge_vex_bench"); var collection = database.GetCollection("vex_observations"); CreateIndexes(collection, cancellationToken); var beforeAllocated = GC.GetTotalAllocatedBytes(); var insertStopwatch = Stopwatch.StartNew(); InsertObservations(collection, _seeds, _config.ResolveBatchSize(), cancellationToken); insertStopwatch.Stop(); var correlationStopwatch = Stopwatch.StartNew(); var documents = collection .Find(FilterDefinition.Empty) .Project(Builders.Projection .Include("tenant") .Include("statements") .Include("linkset")) .ToList(cancellationToken); var aggregator = new VexLinksetAggregator(); lastAggregation = aggregator.Correlate(documents); correlationStopwatch.Stop(); var totalElapsed = insertStopwatch.Elapsed + correlationStopwatch.Elapsed; var afterAllocated = GC.GetTotalAllocatedBytes(); totalDurations[iteration] = totalElapsed.TotalMilliseconds; insertDurations[iteration] = insertStopwatch.Elapsed.TotalMilliseconds; correlationDurations[iteration] = correlationStopwatch.Elapsed.TotalMilliseconds; allocated[iteration] = Math.Max(0, afterAllocated - beforeAllocated) / (1024d * 1024d); var totalSeconds = Math.Max(totalElapsed.TotalSeconds, 0.0001d); observationThroughputs[iteration] = _seeds.Count / totalSeconds; var eventSeconds = Math.Max(correlationStopwatch.Elapsed.TotalSeconds, 0.0001d); var eventCount = Math.Max(lastAggregation.EventCount, 1); eventThroughputs[iteration] = eventCount / eventSeconds; } return new VexScenarioExecutionResult( totalDurations, insertDurations, correlationDurations, allocated, observationThroughputs, eventThroughputs, ObservationCount: _seeds.Count, AliasGroups: _config.ResolveAliasGroups(), StatementCount: lastAggregation.StatementCount, EventCount: lastAggregation.EventCount, AggregationResult: lastAggregation); } private static void InsertObservations( IMongoCollection collection, IReadOnlyList seeds, int batchSize, CancellationToken cancellationToken) { for (var offset = 0; offset < seeds.Count; offset += batchSize) { cancellationToken.ThrowIfCancellationRequested(); var remaining = Math.Min(batchSize, seeds.Count - offset); var batch = new List(remaining); for (var index = 0; index < remaining; index++) { batch.Add(seeds[offset + index].ToBsonDocument()); } collection.InsertMany(batch, new InsertManyOptions { IsOrdered = false, BypassDocumentValidation = true, }, cancellationToken); } } private static void CreateIndexes(IMongoCollection collection, CancellationToken cancellationToken) { var indexKeys = Builders.IndexKeys .Ascending("tenant") .Ascending("linkset.aliases"); try { collection.Indexes.CreateOne(new CreateIndexModel(indexKeys), cancellationToken: cancellationToken); } catch { // non-fatal } } }