Skip to content

Commit 56c6a24

Browse files
authored
Merge pull request #304 from liammclennan/fix-end-to-end-tests
Fix end to end tests
2 parents 6bde141 + c2a7dbb commit 56c6a24

File tree

7 files changed

+237
-51
lines changed

7 files changed

+237
-51
lines changed

src/SeqCli/Cli/Commands/Bench/BenchCasesCollection.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,5 @@ namespace SeqCli.Cli.Commands.Bench;
2222
class BenchCasesCollection
2323
{
2424
// ReSharper disable once CollectionNeverUpdated.Global
25-
public IList<BenchCase> Cases { get; } = new List<BenchCase>();
25+
public IList<QueryBenchCase> Cases { get; } = new List<QueryBenchCase>();
2626
}

src/SeqCli/Cli/Commands/Bench/BenchCommand.cs

Lines changed: 155 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,18 @@
1313
// limitations under the License.
1414

1515
using System;
16+
using System.Collections.Generic;
1617
using System.IO;
1718
using System.Linq;
18-
using System.Security.Cryptography;
19-
using System.Text;
19+
using System.Threading;
2020
using System.Threading.Tasks;
2121
using Newtonsoft.Json;
22+
using Seq.Api;
23+
using Seq.Api.Model.Data;
2224
using Seq.Api.Model.Signals;
2325
using SeqCli.Cli.Features;
2426
using SeqCli.Connection;
27+
using SeqCli.Sample.Loader;
2528
using SeqCli.Util;
2629
using Serilog;
2730
using Serilog.Context;
@@ -70,6 +73,8 @@ class BenchCommand : Command
7073
string _reportingServerUrl = "";
7174
string _reportingServerApiKey = "";
7275
string _description = "";
76+
bool _withIngestion = false;
77+
bool _withQueries = false;
7378

7479
public BenchCommand(SeqConnectionFactory connectionFactory)
7580
{
@@ -96,70 +101,102 @@ public BenchCommand(SeqConnectionFactory connectionFactory)
96101
"description=",
97102
"Optional description of the bench test run",
98103
a => _description = a);
104+
Options.Add(
105+
"with-ingestion",
106+
"Should the benchmark include sending events to Seq",
107+
_ => _withIngestion = true);
108+
Options.Add(
109+
"with-queries",
110+
"Should the benchmark include querying Seq",
111+
_ => _withQueries = true);
99112
}
100113

101114
protected override async Task<int> Run()
102115
{
116+
if (!_withIngestion && !_withQueries)
117+
{
118+
Log.Error("Use at least one of --with-ingestion and --with-queries");
119+
return 1;
120+
}
121+
103122
try
104123
{
124+
var (_, apiKey) = _connectionFactory.GetConnectionDetails(_connection);
105125
var connection = _connectionFactory.Connect(_connection);
106126
var seqVersion = (await connection.Client.GetRootAsync()).Version;
127+
await using var reportingLogger = BuildReportingLogger();
107128

108-
var cases = ReadCases(_cases);
109129
var runId = Guid.NewGuid().ToString("N")[..16];
130+
CancellationTokenSource cancellationTokenSource = new ();
131+
var cancellationToken = cancellationTokenSource.Token;
110132

111-
await using var reportingLogger = BuildReportingLogger();
112-
113-
using (!string.IsNullOrWhiteSpace(_description)
114-
? LogContext.PushProperty("Description", _description)
115-
: null)
116-
{
117-
reportingLogger.Information(
118-
"Bench run {RunId} against {ServerUrl} ({SeqVersion}); {CaseCount} cases, {Runs} runs, from {Start} to {End}",
119-
runId, connection.Client.ServerUrl, seqVersion, cases.Cases.Count, _runs, _range.Start, _range.End);
120-
}
121-
122133
using (LogContext.PushProperty("RunId", runId))
134+
using (LogContext.PushProperty("SeqVersion", seqVersion))
135+
using (LogContext.PushProperty("WithIngestion", _withIngestion))
136+
using (LogContext.PushProperty("WithQueries", _withQueries))
123137
using (LogContext.PushProperty("Start", _range.Start))
124138
using (LogContext.PushProperty("End", _range.End))
139+
using (!string.IsNullOrWhiteSpace(_description)
140+
? LogContext.PushProperty("Description", _description)
141+
: null)
125142
{
126-
foreach (var c in cases.Cases.OrderBy(c => c.Id))
143+
if (_withIngestion)
127144
{
128-
var timings = new BenchCaseTimings();
129-
object? lastResult = null;
145+
var t = IngestionBenchmark(reportingLogger, runId, connection, apiKey, seqVersion,
146+
isQueryBench: _withQueries, cancellationToken)
147+
.ContinueWith(t =>
148+
{
149+
if (t.Exception is not null)
150+
{
151+
return Console.Error.WriteLineAsync(t.Exception.Message);
152+
}
130153

131-
foreach (var i in Enumerable.Range(1, _runs))
154+
return Task.CompletedTask;
155+
});
156+
157+
if (!_withQueries)
132158
{
159+
int benchDurationMs = 120_000;
160+
await Task.Delay(benchDurationMs);
161+
cancellationTokenSource.Cancel();
162+
133163
var response = await connection.Data.QueryAsync(
134-
c.Query,
135-
_range.Start,
136-
_range.End,
137-
c.SignalExpression != null ? SignalExpressionPart.Signal(c.SignalExpression) : null
164+
"select count(*) from stream group by time(1s)",
165+
DateTime.Now.Add(-1 * TimeSpan.FromMilliseconds(benchDurationMs))
138166
);
139-
140-
timings.PushElapsed(response.Statistics.ElapsedMilliseconds);
141-
142-
if (response.Rows != null)
167+
168+
if (response.Slices == null)
143169
{
144-
var isScalarResult = response.Rows.Length == 1 && response.Rows[0].Length == 1;
145-
if (isScalarResult && i == _runs)
146-
{
147-
lastResult = response.Rows[0][0];
148-
}
170+
throw new Exception("Failed to query ingestion benchmark results");
171+
}
172+
173+
var counts = response.Slices.Skip(30) // ignore the warmup
174+
.Select(s => Convert.ToDouble(s.Rows[0][0])) // extract per-second counts
175+
.Where(c => c > 10000) // ignore any very small values
176+
.ToArray();
177+
counts = counts.SkipLast(5).ToArray(); // ignore warmdown
178+
var countsMean = counts.Sum() / counts.Length;
179+
var countsRSD = QueryBenchCaseTimings.StandardDeviation(counts) / countsMean;
180+
181+
using (LogContext.PushProperty("EventsPerSecond", counts))
182+
{
183+
reportingLogger.Information(
184+
"Ingestion benchmark {Description} ran for {RunDuration:N0}ms; ingested {TotalIngested:N0} "
185+
+ "at {EventsPerMinute:N0}events/min; with RSD {RelativeStandardDeviationPercentage,4:N1}%",
186+
_description,
187+
benchDurationMs,
188+
counts.Sum(),
189+
countsMean * 60,
190+
countsRSD * 100);
149191
}
150192
}
193+
}
151194

152-
using (lastResult != null ? LogContext.PushProperty("LastResult", lastResult) : null)
153-
using (!string.IsNullOrWhiteSpace(c.SignalExpression)
154-
? LogContext.PushProperty("SignalExpression", c.SignalExpression)
155-
: null)
156-
using (LogContext.PushProperty("StandardDeviationElapsed", timings.StandardDeviationElapsed))
157-
using (LogContext.PushProperty("Query", c.Query))
158-
{
159-
reportingLogger.Information(
160-
"Case {Id,-40} mean {MeanElapsed,5:N0} ms (first {FirstElapsed,5:N0} ms, min {MinElapsed,5:N0} ms, max {MaxElapsed,5:N0} ms, RSD {RelativeStandardDeviationElapsed,4:N2})",
161-
c.Id, timings.MeanElapsed, timings.FirstElapsed, timings.MinElapsed, timings.MaxElapsed, timings.RelativeStandardDeviationElapsed);
162-
}
195+
if (_withQueries)
196+
{
197+
var collectedTimings = await QueryBenchmark(reportingLogger, runId, connection, seqVersion);
198+
collectedTimings.LogSummary(_description);
199+
cancellationTokenSource.Cancel();
163200
}
164201
}
165202

@@ -172,6 +209,82 @@ protected override async Task<int> Run()
172209
}
173210
}
174211

212+
async Task IngestionBenchmark(Logger reportingLogger, string runId, SeqConnection connection, string? apiKey,
213+
string seqVersion, bool isQueryBench, CancellationToken cancellationToken = default)
214+
{
215+
reportingLogger.Information(
216+
"Ingestion bench run {RunId} against {ServerUrl} ({SeqVersion})",
217+
runId, connection.Client.ServerUrl, seqVersion);
218+
219+
if (isQueryBench)
220+
{
221+
var simulationTasks = Enumerable.Range(1, 500)
222+
.Select(i => Simulation.RunAsync(connection, apiKey, 10000, echoToStdout: false, cancellationToken))
223+
.ToArray();
224+
await Task.Delay(20_000); // how long to ingest before beginning queries
225+
}
226+
else
227+
{
228+
var simulationTasks = Enumerable.Range(1, 2000)
229+
.Select(i => Simulation.RunAsync(connection, apiKey, 10000, echoToStdout: false, cancellationToken))
230+
.ToArray();
231+
}
232+
}
233+
234+
async Task<QueryBenchRunResults> QueryBenchmark(Logger reportingLogger, string runId, SeqConnection connection, string seqVersion)
235+
{
236+
var cases = ReadCases(_cases);
237+
QueryBenchRunResults queryBenchRunResults = new(reportingLogger);
238+
reportingLogger.Information(
239+
"Query benchmark run {RunId} against {ServerUrl} ({SeqVersion}); {CaseCount} cases, {Runs} runs, from {Start} to {End}",
240+
runId, connection.Client.ServerUrl, seqVersion, cases.Cases.Count, _runs, _range.Start, _range.End);
241+
242+
243+
foreach (var c in cases.Cases.OrderBy(c => c.Id)
244+
.Concat(new [] { QueryBenchRunResults.FINAL_COUNT_CASE }))
245+
{
246+
var timings = new QueryBenchCaseTimings(c);
247+
queryBenchRunResults.Add(timings);
248+
249+
foreach (var i in Enumerable.Range(1, _runs))
250+
{
251+
var response = await connection.Data.QueryAsync(
252+
c.Query,
253+
_range.Start,
254+
_range.End,
255+
c.SignalExpression != null ? SignalExpressionPart.Signal(c.SignalExpression) : null,
256+
null,
257+
TimeSpan.FromMinutes(4)
258+
);
259+
260+
timings.PushElapsed(response.Statistics.ElapsedMilliseconds);
261+
262+
if (response.Rows != null)
263+
{
264+
var isScalarResult = response.Rows.Length == 1 && response.Rows[0].Length == 1;
265+
if (isScalarResult && i == _runs)
266+
{
267+
timings.LastResult = response.Rows[0][0];
268+
}
269+
}
270+
}
271+
272+
using (timings.LastResult != null ? LogContext.PushProperty("LastResult", timings.LastResult) : null)
273+
using (!string.IsNullOrWhiteSpace(c.SignalExpression)
274+
? LogContext.PushProperty("SignalExpression", c.SignalExpression)
275+
: null)
276+
using (LogContext.PushProperty("StandardDeviationElapsed", timings.StandardDeviationElapsed))
277+
using (LogContext.PushProperty("Query", c.Query))
278+
{
279+
reportingLogger.Information(
280+
"Case {Id,-40} ({LastResult}) mean {MeanElapsed,5:N0} ms (first {FirstElapsed,5:N0} ms, min {MinElapsed,5:N0} ms, max {MaxElapsed,5:N0} ms, RSD {RelativeStandardDeviationElapsed,4:N2})",
281+
c.Id, timings.LastResult, timings.MeanElapsed, timings.FirstElapsed, timings.MinElapsed, timings.MaxElapsed, timings.RelativeStandardDeviationElapsed);
282+
}
283+
}
284+
285+
return queryBenchRunResults;
286+
}
287+
175288
/// <summary>
176289
/// Build a second Serilog logger for logging benchmark results.
177290
/// </summary>

src/SeqCli/Cli/Commands/Bench/BenchCase.cs renamed to src/SeqCli/Cli/Commands/Bench/QueryBenchCase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ namespace SeqCli.Cli.Commands.Bench;
1616

1717
// ReSharper disable ClassNeverInstantiated.Global AutoPropertyCanBeMadeGetOnly.Global UnusedAutoPropertyAccessor.Global
1818

19-
class BenchCase
19+
class QueryBenchCase
2020
{
2121
public string Id { get; set; } = null!;
2222
public string Query { get; set; } = null!;

src/SeqCli/Cli/Commands/Bench/BenchCaseTimings.cs renamed to src/SeqCli/Cli/Commands/Bench/QueryBenchCaseTimings.cs

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,25 +19,40 @@
1919
namespace SeqCli.Cli.Commands.Bench;
2020

2121
/*
22-
* Collects benchmarking elapsed time measurements and calculates statistics.
22+
* Collects benchmarking elapsed time measurements and calculates statistics.
23+
*
24+
* The results for one bench case.
2325
*/
24-
class BenchCaseTimings
26+
class QueryBenchCaseTimings
2527
{
28+
readonly QueryBenchCase _queryBenchCase;
2629
readonly List<double> _timings = new();
27-
30+
object? _lastResult;
31+
2832
public double MeanElapsed => _timings.Sum() / _timings.Count;
2933
public double MinElapsed => _timings.Min();
3034
public double MaxElapsed => _timings.Max();
3135
public double FirstElapsed => _timings.First();
3236
public double StandardDeviationElapsed => StandardDeviation(_timings);
3337
public double RelativeStandardDeviationElapsed => StandardDeviation(_timings) / MeanElapsed;
3438

39+
public object? LastResult
40+
{
41+
get => _lastResult;
42+
set => _lastResult = value;
43+
}
44+
45+
public QueryBenchCaseTimings(QueryBenchCase queryBenchCase)
46+
{
47+
_queryBenchCase = queryBenchCase;
48+
}
49+
3550
public void PushElapsed(double elapsed)
3651
{
3752
_timings.Add(elapsed);
3853
}
3954

40-
static double StandardDeviation(ICollection<double> population)
55+
public static double StandardDeviation(ICollection<double> population)
4156
{
4257
if (population.Count < 2)
4358
{
@@ -47,4 +62,6 @@ static double StandardDeviation(ICollection<double> population)
4762
var mean = population.Sum() / population.Count;
4863
return Math.Sqrt(population.Select(e => Math.Pow(e - mean, 2)).Sum() / (population.Count - 1));
4964
}
65+
66+
public string Id => _queryBenchCase.Id;
5067
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using Serilog.Core;
5+
6+
namespace SeqCli.Cli.Commands.Bench;
7+
8+
class QueryBenchRunResults
9+
{
10+
readonly Logger _reportingLogger;
11+
List<QueryBenchCaseTimings> _collectedTimings = new();
12+
13+
public static QueryBenchCase FINAL_COUNT_CASE = new()
14+
{
15+
Id = "final-count-star",
16+
Query = "select count(*) from stream",
17+
};
18+
19+
public QueryBenchRunResults(Logger reportingLogger)
20+
{
21+
_reportingLogger = reportingLogger;
22+
}
23+
24+
public void Add(QueryBenchCaseTimings caseTimings)
25+
{
26+
_collectedTimings.Add(caseTimings);
27+
}
28+
29+
public void LogSummary(string description)
30+
{
31+
_reportingLogger.Information(
32+
"Query benchmark {Description} complete in {TotalMeanElapsed:N0} ms with {MeanRelativeStandardDeviationPercentage:N1}% deviation, processed {FinalEventCount:N0} events at {EventsPerMs:N0} events/ms",
33+
description,
34+
TotalMeanElapsed(),
35+
MeanRelativeStandardDeviationPercentage(),
36+
FinalEventCount(),
37+
FinalEventCount() * _collectedTimings.Count / TotalMeanElapsed());
38+
}
39+
40+
private double TotalMeanElapsed()
41+
{
42+
return _collectedTimings.Sum(c => c.MeanElapsed);
43+
}
44+
45+
private double MeanRelativeStandardDeviationPercentage()
46+
{
47+
return _collectedTimings.Average(c => c.RelativeStandardDeviationElapsed) * 100;
48+
}
49+
50+
private int FinalEventCount()
51+
{
52+
var benchCase = _collectedTimings.Single(c => c.Id == FINAL_COUNT_CASE.Id);
53+
return Convert.ToInt32(benchCase.LastResult);
54+
}
55+
}

0 commit comments

Comments
 (0)