1+ // Copyright 2018 Datalust Pty Ltd
2+ //
3+ // Licensed under the Apache License, Version 2.0 (the "License");
4+ // you may not use this file except in compliance with the License.
5+ // You may obtain a copy of the License at
6+ //
7+ // http://www.apache.org/licenses/LICENSE-2.0
8+ //
9+ // Unless required by applicable law or agreed to in writing, software
10+ // distributed under the License is distributed on an "AS IS" BASIS,
11+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ // See the License for the specific language governing permissions and
13+ // limitations under the License.
14+
115#nullable enable
16+
217using System ;
18+ using System . Collections . Generic ;
19+ using System . IO ;
20+ using System . Linq ;
321using System . Threading . Tasks ;
22+ using Newtonsoft . Json ;
23+ using Seq . Api . Model . Signals ;
424using SeqCli . Cli . Features ;
25+ using SeqCli . Connection ;
26+ using Serilog ;
27+ using Serilog . Context ;
28+ using Serilog . Core ;
529
630namespace SeqCli . Cli . Commands ;
731
832[ Command ( "bench" , "Measure query performance" ) ]
933class BenchCommand : Command
1034{
35+ readonly SeqConnectionFactory _connectionFactory ;
1136 int _runs = 0 ;
1237 readonly ConnectionFeature _connection ;
1338 readonly DateRangeFeature _range ;
1439 string _cases = "" ;
15-
40+ string _reportingServerUrl = "" ;
41+ string _reportingServerApiKey = "" ;
42+ ILogger ? _reportingLogger = null ;
43+
1644 const int DefaultRuns = 3 ;
1745
18- public BenchCommand ( )
46+ public BenchCommand ( SeqConnectionFactory connectionFactory )
1947 {
48+ _connectionFactory = connectionFactory ;
2049 Options . Add ( "r|runs=" , "The number of runs to execute" , r =>
2150 {
2251 if ( ! int . TryParse ( r , out _runs ) )
@@ -25,15 +54,102 @@ public BenchCommand()
2554 }
2655 } ) ;
2756
28- Options . Add ( "c|cases=" , "A JSON file containing the set of cases to run. Defaults to a standard set of cases" , c => _cases = c ) ;
29-
57+ Options . Add (
58+ "c|cases=" ,
59+ "A JSON file containing the set of cases to run. Defaults to a standard set of cases" ,
60+ c => _cases = c ) ;
61+
3062 _connection = Enable < ConnectionFeature > ( ) ;
3163 _range = Enable < DateRangeFeature > ( ) ;
64+
65+ Options . Add (
66+ "reporting-server=" ,
67+ "The address of a Seq server to send bench results to" ,
68+ s => _reportingServerUrl = s ) ;
69+ Options . Add (
70+ "reporting-apikey=" ,
71+ "The API key to use when connecting to the reporting server" ,
72+ a => _reportingServerApiKey = a ) ;
3273 }
3374
34- protected override Task < int > Run ( )
75+ protected override async Task < int > Run ( )
3576 {
36- Console . WriteLine ( "Bench command here" ) ;
37- return Task . FromResult ( 0 ) ;
77+ try
78+ {
79+ var connection = _connectionFactory . Connect ( _connection ) ;
80+ _reportingLogger = BuildReportingLogger ( ) ;
81+
82+ var cases = ReadCases ( _cases ) ;
83+
84+ using ( LogContext . PushProperty ( "BenchRunId" , Guid . NewGuid ( ) ) )
85+ {
86+ foreach ( var c in cases . Cases )
87+ {
88+ // TODO: collect results for each run
89+ foreach ( var i in Enumerable . Range ( 1 , _runs ) )
90+ {
91+ var result = await connection . Data . QueryAsync (
92+ c . Query ,
93+ _range . Start ?? DateTime . UtcNow . AddDays ( - 7 ) ,
94+ _range . End ,
95+ SignalExpressionPart . FromIntersectedIds ( c . Signals )
96+ ) ;
97+
98+ var isScalarResult = result . Rows . Length == 1 && result . Rows [ 0 ] . Length == 1 ;
99+ using ( isScalarResult ? LogContext . PushProperty ( "Result" , result . Rows [ 0 ] [ 0 ] ) : null )
100+ using ( LogContext . PushProperty ( "Query" , c . Query ) )
101+ {
102+ _reportingLogger . Information ( "Benchmarked query {Id} in {Elapsed:N0}ms" , c . Id ,
103+ result . Statistics . ElapsedMilliseconds ) ;
104+ }
105+ }
106+ }
107+ }
108+
109+ return 0 ;
110+ } catch ( Exception ex )
111+ {
112+ Log . Error ( ex , "Benchmarking failed: {ErrorMessage}" , ex . Message ) ;
113+ return 1 ;
114+ }
115+ }
116+
117+ Logger BuildReportingLogger ( )
118+ {
119+ return string . IsNullOrWhiteSpace ( _reportingServerUrl )
120+ ? new LoggerConfiguration ( )
121+ . Enrich . FromLogContext ( )
122+ . WriteTo . Console ( )
123+ . CreateLogger ( )
124+ : new LoggerConfiguration ( )
125+ . Enrich . FromLogContext ( )
126+ . WriteTo . Console ( )
127+ . WriteTo . Seq ( _reportingServerUrl , period : TimeSpan . FromMilliseconds ( 1 ) )
128+ . CreateLogger ( ) ;
129+ }
130+
131+ static BenchCasesCollection ReadCases ( string filename )
132+ {
133+ var defaultCasesPath = Path . Combine ( Path . GetDirectoryName ( typeof ( BenchCommand ) . Assembly . Location ) ! , "Cli/Commands/BenchCases.json" ) ;
134+ var casesString = File . ReadAllText ( string . IsNullOrWhiteSpace ( filename )
135+ ? defaultCasesPath
136+ : filename ) ;
137+ return JsonConvert . DeserializeObject < BenchCasesCollection > ( casesString )
138+ ?? new BenchCasesCollection ( ) ;
38139 }
140+ }
141+
142+ class BenchCasesCollection
143+ {
144+ public IList < BenchCase > Cases = new List < BenchCase > ( ) ;
145+ }
146+
147+ /*
148+ * A benchmark test case.
149+ */
150+ class BenchCase
151+ {
152+ public string Id = "" ;
153+ public string Query = "" ;
154+ public IList < string > Signals = new List < string > ( ) ;
39155}
0 commit comments