diff --git a/Foundation.Data.Doublets.Cli.Tests/BenchmarkTests.cs b/Foundation.Data.Doublets.Cli.Tests/BenchmarkTests.cs new file mode 100644 index 0000000..2478b7f --- /dev/null +++ b/Foundation.Data.Doublets.Cli.Tests/BenchmarkTests.cs @@ -0,0 +1,142 @@ +using Xunit; +using System.IO; +using System.Threading.Tasks; + +namespace Foundation.Data.Doublets.Cli.Tests +{ + public class BenchmarkTests + { + [Fact] + public async Task BenchmarkRunner_ShouldRunSuccessfully() + { + // Arrange + var tempDbPath = Path.GetTempFileName(); + try + { + var benchmarkRunner = new BenchmarkRunner(tempDbPath, trace: false); + var options = new BenchmarkOptions + { + TestQueries = new List { "() ((1 1))" }, // Simple create operation + IterationsPerQuery = 2, + WarmupIterations = 1, + ServerPort = 8081 // Use different port to avoid conflicts + }; + + // Act + var results = await benchmarkRunner.RunBenchmarkAsync(options); + + // Assert + Assert.NotNull(results); + Assert.NotNull(results.CliResults); + Assert.NotNull(results.ServerResults); + Assert.True(results.CliResults.TotalOperations > 0); + Assert.True(results.ServerResults.TotalOperations > 0); + Assert.True(results.CliResults.OverallAverageLatencyMs >= 0); + Assert.True(results.ServerResults.OverallAverageLatencyMs >= 0); + } + finally + { + if (File.Exists(tempDbPath)) + { + File.Delete(tempDbPath); + } + } + } + + [Fact] + public void BenchmarkOptions_ShouldHaveDefaults() + { + // Arrange & Act + var options = new BenchmarkOptions(); + + // Assert + Assert.NotNull(options.TestQueries); + Assert.Empty(options.TestQueries); + Assert.Equal(10, options.IterationsPerQuery); + Assert.Equal(3, options.WarmupIterations); + Assert.Equal(8080, options.ServerPort); + } + + [Fact] + public void BenchmarkResults_ShouldPrintReport() + { + // Arrange + var results = new BenchmarkResults + { + CliResults = new AccessMethodResults + { + MethodName = "CLI Test", + TotalOperations = 10, + SuccessfulOperations = 10, + FailedOperations = 0, + OverallAverageLatencyMs = 5.0 + }, + ServerResults = new AccessMethodResults + { + MethodName = "Server Test", + TotalOperations = 10, + SuccessfulOperations = 8, + FailedOperations = 2, + OverallAverageLatencyMs = 10.0 + } + }; + + // Act & Assert - Should not throw + results.PrintReport(); + } + + [Fact] + public void LinoProtocolClient_ShouldConnectAndDisconnect() + { + // Arrange + var client = new LinoProtocolClient("localhost", 8082); + + // Act & Assert - Should not throw when disposing without connecting + client.Dispose(); + + // Test multiple dispose calls + client.Dispose(); + } + + [Fact] + public void LinoRequest_ShouldHaveProperties() + { + // Arrange & Act + var request = new LinoRequest + { + Query = "() ((1 1))", + RequestId = 123, + Timestamp = DateTime.UtcNow + }; + + // Assert + Assert.Equal("() ((1 1))", request.Query); + Assert.Equal(123, request.RequestId); + Assert.True(request.Timestamp != default); + } + + [Fact] + public void LinoResponse_ShouldHaveProperties() + { + // Arrange & Act + var response = new LinoResponse + { + Success = true, + ProcessingTimeMs = 42, + ChangesCount = 1, + Changes = new List + { + new ChangeInfo { Before = null, After = "(1: 1 1)" } + } + }; + + // Assert + Assert.True(response.Success); + Assert.Equal(42, response.ProcessingTimeMs); + Assert.Equal(1, response.ChangesCount); + Assert.Single(response.Changes); + Assert.Null(response.Changes[0].Before); + Assert.Equal("(1: 1 1)", response.Changes[0].After); + } + } +} \ No newline at end of file diff --git a/Foundation.Data.Doublets.Cli/BenchmarkRunner.cs b/Foundation.Data.Doublets.Cli/BenchmarkRunner.cs new file mode 100644 index 0000000..c740e93 --- /dev/null +++ b/Foundation.Data.Doublets.Cli/BenchmarkRunner.cs @@ -0,0 +1,305 @@ +using System.Diagnostics; +using System.Net; + +namespace Foundation.Data.Doublets.Cli +{ + public class BenchmarkRunner + { + private readonly string _databasePath; + private readonly bool _trace; + + public BenchmarkRunner(string databasePath, bool trace = false) + { + _databasePath = databasePath ?? throw new ArgumentNullException(nameof(databasePath)); + _trace = trace; + } + + public async Task RunBenchmarkAsync(BenchmarkOptions options) + { + Console.WriteLine("Starting benchmark: CLI access vs LiNo protocol server access"); + Console.WriteLine($"Test queries: {options.TestQueries.Count}"); + Console.WriteLine($"Iterations per query: {options.IterationsPerQuery}"); + Console.WriteLine($"Warmup iterations: {options.WarmupIterations}"); + Console.WriteLine(); + + var results = new BenchmarkResults(); + + // Benchmark CLI access (direct file access) + Console.WriteLine("Benchmarking CLI access (direct file access)..."); + results.CliResults = await BenchmarkCliAccessAsync(options); + + // Benchmark server access + Console.WriteLine("Benchmarking LiNo protocol server access..."); + results.ServerResults = await BenchmarkServerAccessAsync(options); + + return results; + } + + private async Task BenchmarkCliAccessAsync(BenchmarkOptions options) + { + var results = new AccessMethodResults { MethodName = "CLI Direct Access" }; + var allMeasurements = new List(); + + foreach (var query in options.TestQueries) + { + var queryMeasurements = new List(); + + // Warmup + for (int i = 0; i < options.WarmupIterations; i++) + { + await ExecuteCliQueryAsync(query); + } + + // Actual measurements + for (int i = 0; i < options.IterationsPerQuery; i++) + { + var stopwatch = Stopwatch.StartNew(); + await ExecuteCliQueryAsync(query); + stopwatch.Stop(); + + queryMeasurements.Add(stopwatch.ElapsedMilliseconds); + allMeasurements.Add(stopwatch.ElapsedMilliseconds); + } + + results.QueryResults[query] = new QueryResults + { + AverageLatencyMs = queryMeasurements.Average(), + MinLatencyMs = queryMeasurements.Min(), + MaxLatencyMs = queryMeasurements.Max(), + MedianLatencyMs = GetMedian(queryMeasurements), + SuccessfulOperations = queryMeasurements.Count + }; + + Console.WriteLine($" Query: {query.Substring(0, Math.Min(50, query.Length))}..."); + Console.WriteLine($" Avg: {results.QueryResults[query].AverageLatencyMs:F2}ms"); + } + + results.OverallAverageLatencyMs = allMeasurements.Average(); + results.OverallMinLatencyMs = allMeasurements.Min(); + results.OverallMaxLatencyMs = allMeasurements.Max(); + results.OverallMedianLatencyMs = GetMedian(allMeasurements); + results.TotalOperations = allMeasurements.Count; + results.SuccessfulOperations = allMeasurements.Count; + results.FailedOperations = 0; + + return results; + } + + private async Task BenchmarkServerAccessAsync(BenchmarkOptions options) + { + var results = new AccessMethodResults { MethodName = "LiNo Protocol Server Access" }; + var allMeasurements = new List(); + + using var server = await StartServerAsync(options.ServerPort); + await Task.Delay(1000); // Give server time to start + + foreach (var query in options.TestQueries) + { + var queryMeasurements = new List(); + var successfulOperations = 0; + + // Warmup + for (int i = 0; i < options.WarmupIterations; i++) + { + try + { + await ExecuteServerQueryAsync(query, options.ServerPort); + } + catch + { + // Ignore warmup failures + } + } + + // Actual measurements + for (int i = 0; i < options.IterationsPerQuery; i++) + { + try + { + var stopwatch = Stopwatch.StartNew(); + await ExecuteServerQueryAsync(query, options.ServerPort); + stopwatch.Stop(); + + queryMeasurements.Add(stopwatch.ElapsedMilliseconds); + allMeasurements.Add(stopwatch.ElapsedMilliseconds); + successfulOperations++; + } + catch (Exception ex) + { + Console.WriteLine($" Query failed: {ex.Message}"); + } + } + + if (queryMeasurements.Any()) + { + results.QueryResults[query] = new QueryResults + { + AverageLatencyMs = queryMeasurements.Average(), + MinLatencyMs = queryMeasurements.Min(), + MaxLatencyMs = queryMeasurements.Max(), + MedianLatencyMs = GetMedian(queryMeasurements), + SuccessfulOperations = successfulOperations + }; + + Console.WriteLine($" Query: {query.Substring(0, Math.Min(50, query.Length))}..."); + Console.WriteLine($" Avg: {results.QueryResults[query].AverageLatencyMs:F2}ms"); + } + } + + if (allMeasurements.Any()) + { + results.OverallAverageLatencyMs = allMeasurements.Average(); + results.OverallMinLatencyMs = allMeasurements.Min(); + results.OverallMaxLatencyMs = allMeasurements.Max(); + results.OverallMedianLatencyMs = GetMedian(allMeasurements); + } + + results.TotalOperations = options.TestQueries.Count * options.IterationsPerQuery; + results.SuccessfulOperations = allMeasurements.Count; + results.FailedOperations = results.TotalOperations - results.SuccessfulOperations; + + return results; + } + + private async Task ExecuteCliQueryAsync(string query) + { + var links = new NamedLinksDecorator(_databasePath, _trace); + var options = new AdvancedMixedQueryProcessor.Options { Query = query }; + + await Task.Run(() => AdvancedMixedQueryProcessor.ProcessQuery(links, options)); + + // Add small delay to ensure file handles are released + await Task.Delay(10); + } + + private async Task ExecuteServerQueryAsync(string query, int port) + { + using var client = new LinoProtocolClient("localhost", port); + await client.ConnectAsync(); + var response = await client.SendQueryAsync(query); + + if (!response.Success) + { + throw new Exception($"Server query failed: {response.Error}"); + } + } + + private Task StartServerAsync(int port) + { + // Create a separate database path for the server to avoid file locking conflicts + var serverDbPath = _databasePath.Replace(".links", ".server.links"); + + // Copy the main database to the server database if it exists and server db doesn't exist + if (File.Exists(_databasePath) && !File.Exists(serverDbPath)) + { + File.Copy(_databasePath, serverDbPath); + + // Also copy the names database if it exists + var mainNamesDbPath = _databasePath.Replace(".links", ".names.links"); + var serverNamesDbPath = serverDbPath.Replace(".links", ".names.links"); + if (File.Exists(mainNamesDbPath)) + { + File.Copy(mainNamesDbPath, serverNamesDbPath); + } + } + + var links = new NamedLinksDecorator(serverDbPath, _trace); + var server = new LinoProtocolServer(links, IPAddress.Loopback, port); + + Task.Run(async () => await server.StartAsync()); + + return Task.FromResult(server); + } + + private static double GetMedian(List values) + { + if (!values.Any()) return 0; + + var sorted = values.OrderBy(x => x).ToList(); + var count = sorted.Count; + + if (count % 2 == 0) + { + return (sorted[count / 2 - 1] + sorted[count / 2]) / 2.0; + } + else + { + return sorted[count / 2]; + } + } + } + + public class BenchmarkOptions + { + public List TestQueries { get; set; } = new List(); + public int IterationsPerQuery { get; set; } = 10; + public int WarmupIterations { get; set; } = 3; + public int ServerPort { get; set; } = 8080; + } + + public class BenchmarkResults + { + public AccessMethodResults? CliResults { get; set; } + public AccessMethodResults? ServerResults { get; set; } + + public void PrintReport() + { + Console.WriteLine(); + Console.WriteLine("=== BENCHMARK RESULTS ==="); + Console.WriteLine(); + + if (CliResults != null) + { + PrintAccessMethodResults(CliResults); + } + + if (ServerResults != null) + { + PrintAccessMethodResults(ServerResults); + } + + if (CliResults != null && ServerResults != null) + { + Console.WriteLine("=== COMPARISON ==="); + Console.WriteLine($"CLI vs Server Latency Ratio: {CliResults.OverallAverageLatencyMs / ServerResults.OverallAverageLatencyMs:F2}x"); + Console.WriteLine($"CLI Success Rate: {(double)CliResults.SuccessfulOperations / CliResults.TotalOperations * 100:F1}%"); + Console.WriteLine($"Server Success Rate: {(double)ServerResults.SuccessfulOperations / ServerResults.TotalOperations * 100:F1}%"); + } + } + + private void PrintAccessMethodResults(AccessMethodResults results) + { + Console.WriteLine($"--- {results.MethodName} ---"); + Console.WriteLine($"Total Operations: {results.TotalOperations}"); + Console.WriteLine($"Successful Operations: {results.SuccessfulOperations}"); + Console.WriteLine($"Failed Operations: {results.FailedOperations}"); + Console.WriteLine($"Overall Average Latency: {results.OverallAverageLatencyMs:F2}ms"); + Console.WriteLine($"Overall Min Latency: {results.OverallMinLatencyMs}ms"); + Console.WriteLine($"Overall Max Latency: {results.OverallMaxLatencyMs}ms"); + Console.WriteLine($"Overall Median Latency: {results.OverallMedianLatencyMs:F2}ms"); + Console.WriteLine(); + } + } + + public class AccessMethodResults + { + public string MethodName { get; set; } = string.Empty; + public Dictionary QueryResults { get; set; } = new Dictionary(); + public double OverallAverageLatencyMs { get; set; } + public long OverallMinLatencyMs { get; set; } + public long OverallMaxLatencyMs { get; set; } + public double OverallMedianLatencyMs { get; set; } + public int TotalOperations { get; set; } + public int SuccessfulOperations { get; set; } + public int FailedOperations { get; set; } + } + + public class QueryResults + { + public double AverageLatencyMs { get; set; } + public long MinLatencyMs { get; set; } + public long MaxLatencyMs { get; set; } + public double MedianLatencyMs { get; set; } + public int SuccessfulOperations { get; set; } + } +} \ No newline at end of file diff --git a/Foundation.Data.Doublets.Cli/Foundation.Data.Doublets.Cli.csproj b/Foundation.Data.Doublets.Cli/Foundation.Data.Doublets.Cli.csproj index 1d8f9ab..cca031a 100644 --- a/Foundation.Data.Doublets.Cli/Foundation.Data.Doublets.Cli.csproj +++ b/Foundation.Data.Doublets.Cli/Foundation.Data.Doublets.Cli.csproj @@ -26,6 +26,7 @@ + diff --git a/Foundation.Data.Doublets.Cli/LinoProtocolClient.cs b/Foundation.Data.Doublets.Cli/LinoProtocolClient.cs new file mode 100644 index 0000000..88086e6 --- /dev/null +++ b/Foundation.Data.Doublets.Cli/LinoProtocolClient.cs @@ -0,0 +1,81 @@ +using System.Net.Sockets; +using System.Text; +using System.Text.Json; + +namespace Foundation.Data.Doublets.Cli +{ + public class LinoProtocolClient : IDisposable + { + private TcpClient? _tcpClient; + private NetworkStream? _stream; + private StreamWriter? _writer; + private StreamReader? _reader; + private readonly string _host; + private readonly int _port; + private bool _disposed = false; + + public LinoProtocolClient(string host, int port) + { + _host = host ?? throw new ArgumentNullException(nameof(host)); + _port = port; + } + + public async Task ConnectAsync() + { + _tcpClient = new TcpClient(); + await _tcpClient.ConnectAsync(_host, _port); + _stream = _tcpClient.GetStream(); + _writer = new StreamWriter(_stream, Encoding.UTF8); + _reader = new StreamReader(_stream, Encoding.UTF8); + } + + public async Task SendQueryAsync(string query) + { + if (_writer == null || _reader == null) + { + throw new InvalidOperationException("Client not connected. Call ConnectAsync first."); + } + + var request = new LinoRequest + { + Query = query, + RequestId = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), + Timestamp = DateTime.UtcNow + }; + + var requestJson = JsonSerializer.Serialize(request); + await _writer.WriteLineAsync(requestJson); + await _writer.FlushAsync(); + + var responseJson = await _reader.ReadLineAsync(); + if (string.IsNullOrEmpty(responseJson)) + { + throw new InvalidOperationException("Received empty response from server"); + } + + var response = JsonSerializer.Deserialize(responseJson); + return response ?? throw new InvalidOperationException("Failed to deserialize server response"); + } + + public void Disconnect() + { + _writer?.Dispose(); + _reader?.Dispose(); + _stream?.Dispose(); + _tcpClient?.Dispose(); + _writer = null; + _reader = null; + _stream = null; + _tcpClient = null; + } + + public void Dispose() + { + if (!_disposed) + { + Disconnect(); + _disposed = true; + } + } + } +} \ No newline at end of file diff --git a/Foundation.Data.Doublets.Cli/LinoProtocolServer.cs b/Foundation.Data.Doublets.Cli/LinoProtocolServer.cs new file mode 100644 index 0000000..fab2899 --- /dev/null +++ b/Foundation.Data.Doublets.Cli/LinoProtocolServer.cs @@ -0,0 +1,168 @@ +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Text.Json; +using Platform.Data.Doublets; +using Platform.Protocols.Lino; + +namespace Foundation.Data.Doublets.Cli +{ + public class LinoProtocolServer : IDisposable + { + private readonly NamedLinksDecorator _links; + private readonly TcpListener _listener; + private readonly CancellationTokenSource _cancellationTokenSource; + private bool _disposed = false; + + public LinoProtocolServer(NamedLinksDecorator links, IPAddress address, int port) + { + _links = links ?? throw new ArgumentNullException(nameof(links)); + _listener = new TcpListener(address, port); + _cancellationTokenSource = new CancellationTokenSource(); + } + + public async Task StartAsync() + { + _listener.Start(); + Console.WriteLine($"LiNo Protocol Server started on {_listener.LocalEndpoint}"); + + try + { + while (!_cancellationTokenSource.Token.IsCancellationRequested) + { + var tcpClient = await _listener.AcceptTcpClientAsync(); + Task.Run(async () => await HandleClientAsync(tcpClient, _cancellationTokenSource.Token)); + } + } + catch (ObjectDisposedException) + { + // Expected when stopping + } + } + + public void Stop() + { + _cancellationTokenSource.Cancel(); + _listener?.Stop(); + } + + private async Task HandleClientAsync(TcpClient client, CancellationToken cancellationToken) + { + using (client) + using (var stream = client.GetStream()) + using (var reader = new StreamReader(stream, Encoding.UTF8)) + using (var writer = new StreamWriter(stream, Encoding.UTF8)) + { + try + { + while (!cancellationToken.IsCancellationRequested && client.Connected) + { + var request = await reader.ReadLineAsync(); + if (string.IsNullOrEmpty(request)) + break; + + var response = await ProcessRequestAsync(request); + await writer.WriteLineAsync(response); + await writer.FlushAsync(); + } + } + catch (Exception ex) + { + Console.WriteLine($"Error handling client: {ex.Message}"); + } + } + } + + private async Task ProcessRequestAsync(string request) + { + try + { + var requestObj = JsonSerializer.Deserialize(request); + if (requestObj == null) + { + return CreateErrorResponse("Invalid request format"); + } + + var stopwatch = System.Diagnostics.Stopwatch.StartNew(); + + var changesList = new List<(Platform.Data.Doublets.Link Before, Platform.Data.Doublets.Link After)>(); + + var options = new AdvancedMixedQueryProcessor.Options + { + Query = requestObj.Query, + Trace = false, + ChangesHandler = (beforeLink, afterLink) => + { + changesList.Add((new Platform.Data.Doublets.Link(beforeLink), new Platform.Data.Doublets.Link(afterLink))); + return _links.Constants.Continue; + } + }; + + await Task.Run(() => AdvancedMixedQueryProcessor.ProcessQuery(_links, options)); + + stopwatch.Stop(); + + var response = new LinoResponse + { + Success = true, + ProcessingTimeMs = stopwatch.ElapsedMilliseconds, + ChangesCount = changesList.Count, + Changes = changesList.Select(c => new ChangeInfo + { + Before = c.Before.IsNull() ? null : ((ILinks)_links).Format(c.Before), + After = c.After.IsNull() ? null : ((ILinks)_links).Format(c.After) + }).ToList() + }; + + return JsonSerializer.Serialize(response); + } + catch (Exception ex) + { + return CreateErrorResponse($"Error processing request: {ex.Message}"); + } + } + + private string CreateErrorResponse(string error) + { + var response = new LinoResponse + { + Success = false, + Error = error + }; + return JsonSerializer.Serialize(response); + } + + public void Dispose() + { + if (!_disposed) + { + _cancellationTokenSource?.Cancel(); + _listener?.Stop(); + _cancellationTokenSource?.Dispose(); + _disposed = true; + } + } + } + + public class LinoRequest + { + public string? Query { get; set; } + public long RequestId { get; set; } + public DateTime Timestamp { get; set; } + } + + public class LinoResponse + { + public bool Success { get; set; } + public string? Error { get; set; } + public long ProcessingTimeMs { get; set; } + public int ChangesCount { get; set; } + public List? Changes { get; set; } + } + + public class ChangeInfo + { + public string? Before { get; set; } + public string? After { get; set; } + } +} \ No newline at end of file diff --git a/Foundation.Data.Doublets.Cli/Program.cs b/Foundation.Data.Doublets.Cli/Program.cs index 1f9bfed..d254548 100644 --- a/Foundation.Data.Doublets.Cli/Program.cs +++ b/Foundation.Data.Doublets.Cli/Program.cs @@ -70,6 +70,77 @@ afterOption.AddAlias("--links"); afterOption.AddAlias("-a"); +// Create benchmark subcommand +var benchmarkCommand = new Command("benchmark", "Run benchmark comparing CLI access vs LiNo protocol server access"); + +var iterationsOption = new Option( + name: "--iterations", + description: "Number of iterations per query", + getDefaultValue: () => 10 +); +iterationsOption.AddAlias("-i"); + +var warmupOption = new Option( + name: "--warmup", + description: "Number of warmup iterations", + getDefaultValue: () => 3 +); +warmupOption.AddAlias("-w"); + +var serverPortOption = new Option( + name: "--server-port", + description: "Port for the benchmark server", + getDefaultValue: () => 8080 +); +serverPortOption.AddAlias("-p"); + +var testQueriesOption = new Option( + name: "--queries", + description: "Test queries to benchmark (if not specified, uses default set)" +); +testQueriesOption.AddAlias("-q"); + +benchmarkCommand.AddOption(dbOption); +benchmarkCommand.AddOption(traceOption); +benchmarkCommand.AddOption(iterationsOption); +benchmarkCommand.AddOption(warmupOption); +benchmarkCommand.AddOption(serverPortOption); +benchmarkCommand.AddOption(testQueriesOption); + +benchmarkCommand.SetHandler(async (string db, bool trace, int iterations, int warmup, int serverPort, string[] testQueries) => +{ + var defaultQueries = new[] + { + "() ((1 1))", // Create single link + "() ((1 1) (2 2))", // Create multiple links + "((($i: $s $t)) (($i: $s $t)))", // Read all links + "((1: 1 1)) ((1: 1 2))", // Update single link + "((1 2)) ()" // Delete link (will only work if link exists) + }; + + var queries = testQueries?.Any() == true ? testQueries.ToList() : defaultQueries.ToList(); + + var benchmarkRunner = new BenchmarkRunner(db, trace); + var options = new BenchmarkOptions + { + TestQueries = queries, + IterationsPerQuery = iterations, + WarmupIterations = warmup, + ServerPort = serverPort + }; + + try + { + var results = await benchmarkRunner.RunBenchmarkAsync(options); + results.PrintReport(); + } + catch (Exception ex) + { + Console.Error.WriteLine($"Benchmark failed: {ex.Message}"); + Environment.Exit(1); + } +}, dbOption, traceOption, iterationsOption, warmupOption, serverPortOption, testQueriesOption); + var rootCommand = new RootCommand("LiNo CLI Tool for managing links data store") { dbOption, @@ -79,7 +150,8 @@ structureOption, beforeOption, changesOption, - afterOption + afterOption, + benchmarkCommand }; rootCommand.SetHandler( diff --git a/examples/benchmark_demo.sh b/examples/benchmark_demo.sh new file mode 100755 index 0000000..1a65487 --- /dev/null +++ b/examples/benchmark_demo.sh @@ -0,0 +1,55 @@ +#!/bin/bash + +# Benchmark CLI access vs LiNo protocol server access demo +# This script demonstrates the benchmark functionality implemented for issue #31 + +echo "=== LiNo CLI Benchmark Demo ===" +echo "" +echo "This demo shows the benchmark functionality that compares:" +echo "1. CLI direct file access" +echo "2. LiNo protocol server access" +echo "" + +# Clean up any existing database files +rm -f /tmp/demo.links /tmp/demo.names.links /tmp/demo.server.links /tmp/demo.server.names.links + +echo "Creating some initial data..." + +# Create a few links using CLI +echo "Adding initial data with CLI:" +dotnet run --project ../Foundation.Data.Doublets.Cli -- --db "/tmp/demo.links" '() ((1 1))' --changes --after +echo "" + +dotnet run --project ../Foundation.Data.Doublets.Cli -- --db "/tmp/demo.links" '() ((2 2))' --changes --after +echo "" + +dotnet run --project ../Foundation.Data.Doublets.Cli -- --db "/tmp/demo.links" '() ((1 2))' --changes --after +echo "" + +echo "Current database state:" +dotnet run --project ../Foundation.Data.Doublets.Cli -- --db "/tmp/demo.links" --after +echo "" + +echo "Now running benchmark with minimal iterations to demonstrate functionality..." +echo "" + +# Run benchmark with minimal settings to avoid file locking issues +dotnet run --project ../Foundation.Data.Doublets.Cli -- --db "/tmp/demo.links" benchmark --iterations 1 --warmup 0 --server-port 8082 --queries "(((\$i: \$s \$t)) ((\$i: \$s \$t)))" + +echo "" +echo "Demo completed!" +echo "" +echo "Note: The benchmark implementation includes:" +echo "- LinoProtocolServer.cs: TCP/IP server for LiNo protocol" +echo "- LinoProtocolClient.cs: Client for connecting to the server" +echo "- BenchmarkRunner.cs: Orchestrates the comparison" +echo "- Integration into Program.cs as a subcommand" +echo "" +echo "Usage: clink benchmark [options]" +echo " --iterations: Number of test iterations per query" +echo " --warmup: Number of warmup iterations" +echo " --server-port: Port for benchmark server" +echo " --queries: Custom queries to test" + +# Clean up +rm -f /tmp/demo.links /tmp/demo.names.links /tmp/demo.server.links /tmp/demo.server.names.links \ No newline at end of file