/// <summary> /// Validate arguments. /// </summary> private void ValidateArguments() { if (ValidateArgumentsEx()) { if (Threads <= 0) { throw new Exception("Threads must be positive: " + Threads); } if (Warmup < 0) { throw new Exception("Warmup cannot be negative: " + Warmup); } if (Duration < 0) { throw new Exception("Duration cannot be negative: " + Duration); } if (BatchSize <= 0) { throw new Exception("BatchSize must be positive: " + BatchSize); } if (MaxErrors < 0) { throw new Exception("MaxErrors cannot be negative: " + MaxErrors); } if (ResultWriter == null || !ResultWriter.ToLower().Equals(ResultWriterConsole) && !ResultWriter.ToLower().Equals(ResultWriterFile)) { throw new Exception("Invalid ResultWriter: " + ResultWriter); } if (ResultWriter.ToLower().Equals(ResultWriterFile) && ResultFolder == null) { throw new Exception("ResultFolder must be set for file result writer."); } if (ResultBucketCount <= 0) { throw new Exception("ResultBucketCount must be positive: " + ResultBucketCount); } if (ResultBucketInterval <= 0) { throw new Exception("ResultBucketInterval must be positive: " + ResultBucketInterval); } } }
/// <summary> /// Run the benchmark. /// </summary> public void Run() { PrintDebug("Started benchmark: " + this); ValidateArguments(); if (ResultWriter.ToLower().Equals(ResultWriterConsole)) { _writer = new BenchmarkConsoleResultWriter(); } else { _writer = new BenchmarkFileResultWriter(); } OnStarted(); PrintDebug("Benchmark setup finished."); try { _descs = new List <BenchmarkOperationDescriptor>(); GetDescriptors(_descs); if (_descs.Count == 0) { throw new Exception("No tasks provided for benchmark."); } // Initialize writer. var opNames = new List <string>(_descs.Select(desc => desc.Name)); PrintDebug(() => { var sb = new StringBuilder("Operations: "); foreach (var opName in opNames) { sb.Append(opName).Append(" "); } return(sb.ToString()); }); _writer.Initialize(this, opNames); PrintDebug("Initialized result writer."); // Start worker threads. _tasks = new List <BenchmarkTask>(Threads); PrintDebug("Starting worker threads: " + Threads); for (var i = 0; i < Threads; i++) { var task = new BenchmarkTask(this, _descs); _tasks.Add(task); new Thread(task.Run).Start(); } PrintDebug("Waiting worker threads to start: " + Threads); // Await for all threads to start in spin loop. while (Thread.VolatileRead(ref _readyThreads) < Threads) { Thread.Sleep(10); } PrintDebug("Worker threads started: " + Threads); // Start throughput writer thread. var writerThread = new Thread(new ThroughputTask(this).Run) { IsBackground = true }; writerThread.Start(); PrintDebug("Started throughput writer thread."); // Start warmup thread if needed. if (Warmup > 0) { var thread = new Thread(new WarmupTask(this, Warmup).Run) { IsBackground = true }; thread.Start(); PrintDebug("Started warmup timeout thread: " + Warmup); } else { _warmup = false; } _barrier = new Barrier(Threads, b => { Console.WriteLine("Warmup finished."); _totalWatch.Start(); }); // Start timeout thread if needed. if (Duration > 0) { if (Operations > 0) { PrintDebug("Duration argument is ignored because operations number is set: " + Operations); } else { var thread = new Thread(new TimeoutTask(this, Warmup + Duration).Run) { IsBackground = true }; thread.Start(); PrintDebug("Started duration timeout thread: " + Duration); } } // Let workers start execution. _start = true; // Await workers completion. PrintDebug("Awaiting worker threads completion."); Monitor.Enter(this); try { while (_finishedThreads < Threads) { Monitor.Wait(this); } } finally { Monitor.Exit(this); } PrintDebug("Worker threads completed."); } finally { OnFinished(); _totalWatch.Stop(); PrintDebug("Tear down invoked."); if (PrintThroughputInfo()) { var avgThroughput = _totalWatch.ElapsedMilliseconds == 0 ? 0 : _curOps * 1000 / _totalWatch.ElapsedMilliseconds; var avgLatency = _curOps == 0 ? 0 : (double)_totalWatch.ElapsedMilliseconds * Threads / _curOps; Console.WriteLine("Finishing benchmark [name=" + GetType().Name + ", time=" + _totalWatch.ElapsedMilliseconds + "ms, ops=" + _curOps + ", threads=" + Threads + ", avgThroughput=" + avgThroughput + ", avgLatency=" + string.Format("{0:0.000}ms", avgLatency) + ']'); } else { Console.WriteLine("Finishing benchmark [name=" + GetType().Name + ", time=" + _totalWatch.ElapsedMilliseconds + "ms, ops=" + Operations + ", threads=" + Threads + ']'); } } _percentiles = new Dictionary <string, long[]>(_descs.Count); foreach (var desc in _descs) { _percentiles[desc.Name] = new long[ResultBucketCount]; } foreach (var task in _tasks) { task.CollectPercentiles(_percentiles); } foreach (var percentile in _percentiles) { _writer.WritePercentiles(percentile.Key, ResultBucketInterval, percentile.Value); } _writer.Commit(); PrintDebug("Results committed to output writer."); }