diff --git a/.idea/.idea.Pulse/.idea/.gitignore b/.idea/.idea.Pulse/.idea/.gitignore deleted file mode 100644 index dcbee1e..0000000 --- a/.idea/.idea.Pulse/.idea/.gitignore +++ /dev/null @@ -1,13 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Rider ignored files -/modules.xml -/.idea.Pulse.iml -/projectSettingsUpdater.xml -/contentModel.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/.idea.Pulse/.idea/encodings.xml b/.idea/.idea.Pulse/.idea/encodings.xml deleted file mode 100644 index df87cf9..0000000 --- a/.idea/.idea.Pulse/.idea/encodings.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/.idea.Pulse/.idea/indexLayout.xml b/.idea/.idea.Pulse/.idea/indexLayout.xml deleted file mode 100644 index 7b08163..0000000 --- a/.idea/.idea.Pulse/.idea/indexLayout.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/.idea.Pulse/.idea/vcs.xml b/.idea/.idea.Pulse/.idea/vcs.xml deleted file mode 100644 index 94a25f7..0000000 --- a/.idea/.idea.Pulse/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/Changelog.md b/Changelog.md index fac073e..b8d3664 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,15 @@ # Changelog +## Version 1.1.0.0 + +- Export styling was reworked for much prettier ui +- Enhanced styling of verbose progress reporting +- Optimized interlocking variables to match hardware cache line size and reduce thread contention +- Optimized tasks creation to increase maximum concurrency level +- Summary will now filter out latency and size IQR outliers +- `terms-of-use` is now a command, not a flag +- When `-n` (amount of requests) is set to 1, verbose output will be enforced for better readability + ## Version 1.0.7.0 - Added `get-schema` command to generate a json schema file diff --git a/Readme.md b/Readme.md index 7cf324e..5975834 100644 --- a/Readme.md +++ b/Readme.md @@ -129,10 +129,12 @@ Special: get-sample : command - generates sample file get-schema : command - generates a json schema file check-for-updates: command - checks for updates + terms-of-use : print the terms of use --noop : print selected configuration but don't run -u, --url : override the url of the request -h, --help : print this help text - --terms-of-use : print the terms of use +Notes: + * when "-n" is 1, verbose output is enabled ``` - `-v` or `--verbose` - display verbose output, this changes the output format, instead of displaying a dashboard, it prints requests/responses as they are being processed. diff --git a/src/Pulse/Core/Exporter.cs b/src/Pulse/Core/Exporter.cs index 0b40044..ed6ea36 100644 --- a/src/Pulse/Core/Exporter.cs +++ b/src/Pulse/Core/Exporter.cs @@ -16,8 +16,10 @@ public static async Task ExportHtmlAsync(Response result, string path, bool form string filename = Path.Join(path, $"response-{result.Id}-status-code-{(int)statusCode}.html"); string frameTitle; string content = string.IsNullOrWhiteSpace(result.Content) ? string.Empty : result.Content; + string status; if (result.Exception.IsDefault) { + status = $"{statusCode} ({(int)statusCode})"; frameTitle = "Content:"; if (formatJson) { try { @@ -29,6 +31,7 @@ public static async Task ExportHtmlAsync(Response result, string path, bool form } content = content.Replace('\'', '\"'); } else { + status = "Exception (0)"; frameTitle = "Exception:"; content = $"
{DefaultJsonContext.SerializeException(result.Exception)}
"; } @@ -49,83 +52,196 @@ public static async Task ExportHtmlAsync(Response result, string path, bool form if (result.Headers.Any()) { headers = $""" -
+
{ToHtmlTable(result.Headers)}
"""; } - string body = -$$""" - - -Response: {{result.Id}} - - - -

Response: {{result.Id}}

-
-

StatusCode: {{statusCode}} ({{(int)statusCode}})

+
+

Response: {{result.Id}}

+

{{status}}

{{headers}} +
{{contentFrame}} +
"""; await File.WriteAllTextAsync(filename, body, token); @@ -142,6 +258,9 @@ internal static string ToHtmlTable(IEnumerable"); + sb.AppendLine(""); + sb.AppendLine(""); + sb.AppendLine(""); sb.AppendLine(""); sb.AppendLine(""); sb.AppendLine("Header"); diff --git a/src/Pulse/Core/IPulseMonitor.cs b/src/Pulse/Core/IPulseMonitor.cs index c076b50..cb6d6dd 100644 --- a/src/Pulse/Core/IPulseMonitor.cs +++ b/src/Pulse/Core/IPulseMonitor.cs @@ -38,7 +38,7 @@ public static IPulseMonitor Create(HttpClient client, Request requestRecipe, Par /// Request execution context /// internal sealed class RequestExecutionContext { - private volatile int _currentConcurrentConnections; + private PaddedULong _currentConcurrentConnections; /// /// Sends a request @@ -59,10 +59,10 @@ public async Task SendRequest(int id, Request requestRecipe, HttpClien using var message = requestRecipe.CreateMessage(); long start = Stopwatch.GetTimestamp(), end = 0; try { - currentConcurrencyLevel = Interlocked.Increment(ref _currentConcurrentConnections); + currentConcurrencyLevel = (int)Interlocked.Increment(ref _currentConcurrentConnections.Value); using var response = await httpClient.SendAsync(message, HttpCompletionOption.ResponseHeadersRead, cancellationToken); end = Stopwatch.GetTimestamp(); - Interlocked.Decrement(ref _currentConcurrentConnections); + Interlocked.Decrement(ref _currentConcurrentConnections.Value); statusCode = response.StatusCode; headers = response.Headers; var length = response.Content.Headers.ContentLength; diff --git a/src/Pulse/Core/PaddedULong.cs b/src/Pulse/Core/PaddedULong.cs new file mode 100644 index 0000000..c89ed91 --- /dev/null +++ b/src/Pulse/Core/PaddedULong.cs @@ -0,0 +1,8 @@ +using System.Runtime.InteropServices; + +namespace Pulse.Core; + +[StructLayout(LayoutKind.Sequential, Size = 64)] +internal struct PaddedULong { + public ulong Value; +} \ No newline at end of file diff --git a/src/Pulse/Core/Pulse.cs b/src/Pulse/Core/Pulse.cs index 61f4aa2..eeff5c5 100644 --- a/src/Pulse/Core/Pulse.cs +++ b/src/Pulse/Core/Pulse.cs @@ -65,18 +65,21 @@ internal static async Task RunBounded(Parameters parameters, RequestDetails requ using var semaphore = new SemaphoreSlim(parameters.MaxConnections); - var tasks = Enumerable.Range(1, parameters.Requests) - .AsParallel() - .Select(async requestId => { - try { - await semaphore.WaitAsync(cancellationToken); - await monitor.SendAsync(requestId); - } finally { - semaphore.Release(); - } + var tasks = new Task[parameters.Requests]; + + for (int i = 0; i < parameters.Requests; i++) { + await semaphore.WaitAsync(cancellationToken); + +#pragma warning disable IDE0053 // Use expression body for lambda expression + // lambda expression will change return type + tasks[i] = monitor.SendAsync(i + 1).ContinueWith(_ => { + semaphore.Release(); }); +#pragma warning restore IDE0053 // Use expression body for lambda expression + + } - await Task.WhenAll(tasks).WaitAsync(cancellationToken).ConfigureAwait(false); + await Task.WhenAll(tasks.AsSpan()).WaitAsync(cancellationToken).ConfigureAwait(false); var result = monitor.Consolidate(); @@ -105,11 +108,13 @@ internal static async Task RunUnbounded(Parameters parameters, RequestDetails re var monitor = IPulseMonitor.Create(httpClient, requestDetails.Request, parameters); - var tasks = Enumerable.Range(1, parameters.Requests) - .AsParallel() - .Select(monitor.SendAsync); + var tasks = new Task[parameters.Requests]; + + for (int i = 0; i < parameters.Requests; i++) { + tasks[i] = monitor.SendAsync(i + 1); + } - await Task.WhenAll(tasks).WaitAsync(cancellationToken).ConfigureAwait(false); + await Task.WhenAll(tasks.AsSpan()).WaitAsync(cancellationToken).ConfigureAwait(false); var result = monitor.Consolidate(); diff --git a/src/Pulse/Core/PulseMonitor.cs b/src/Pulse/Core/PulseMonitor.cs index d66cb89..f8a085d 100644 --- a/src/Pulse/Core/PulseMonitor.cs +++ b/src/Pulse/Core/PulseMonitor.cs @@ -29,7 +29,7 @@ public sealed class PulseMonitor : IPulseMonitor { /// /// Current number of responses received /// - private volatile int _responses; + private PaddedULong _responses; // response status code counter // 0: exception @@ -38,7 +38,7 @@ public sealed class PulseMonitor : IPulseMonitor { // 3: 3xx // 4: 4xx // 5: 5xx - private readonly int[] _stats = new int[6]; + private readonly PaddedULong[] _stats = new PaddedULong[6]; private readonly RequestExecutionContext _requestExecutionContext; private readonly int _requestCount; @@ -67,10 +67,10 @@ public PulseMonitor(HttpClient client, Request requestRecipe, Parameters paramet /// public async Task SendAsync(int requestId) { var result = await _requestExecutionContext.SendRequest(requestId, _requestRecipe, _httpClient, _saveContent, _cancellationToken); - Interlocked.Increment(ref _responses); + Interlocked.Increment(ref _responses.Value); // Increment stats int index = (int)result.StatusCode / 100; - Interlocked.Increment(ref _stats[index]); + Interlocked.Increment(ref _stats[index].Value); // Print metrics PrintMetrics(); _results.Push(result); @@ -83,9 +83,9 @@ private void PrintMetrics() { lock (_lock) { var elapsed = Stopwatch.GetElapsedTime(_start).TotalMilliseconds; - var eta = TimeSpan.FromMilliseconds(elapsed / _responses * (_requestCount - _responses)); + var eta = TimeSpan.FromMilliseconds(elapsed / _responses.Value * (_requestCount - (int)_responses.Value)); - double sr = Math.Round((double)_stats[2] / _responses * 100, 2); + double sr = Math.Round((double)_stats[2].Value / _responses.Value * 100, 2); var currentLine = GetCurrentLine(); // Clear @@ -93,7 +93,7 @@ private void PrintMetrics() { // Line 1 Error.Write("Completed: "); SetColors(Color.Yellow, Color.DefaultBackgroundColor); - Error.Write(_responses); + Error.Write(_responses.Value); ResetColors(); Error.Write('/'); SetColors(Color.Yellow, Color.DefaultBackgroundColor); @@ -104,33 +104,33 @@ private void PrintMetrics() { Error.Write(sr); ResetColors(); Error.Write("%, ETA: "); - Write(Utils.DateAndTime.FormatTimeSpan(eta, _etaBuffer), OutputPipe.Error, Color.Yellow, Color.DefaultBackgroundColor); + Write(Utils.DateAndTime.FormatTimeSpan(eta, _etaBuffer), OutputPipe.Error, Color.Yellow); NewLine(OutputPipe.Error); // Line 2 Error.Write("1xx: "); SetColors(Color.White, Color.DefaultBackgroundColor); - Error.Write(_stats[1]); + Error.Write(_stats[1].Value); ResetColors(); Error.Write(", 2xx: "); SetColors(Color.Green, Color.DefaultBackgroundColor); - Error.Write(_stats[2]); + Error.Write(_stats[2].Value); ResetColors(); Error.Write(", 3xx: "); SetColors(Color.Yellow, Color.DefaultBackgroundColor); - Error.Write(_stats[3]); + Error.Write(_stats[3].Value); ResetColors(); Error.Write(", 4xx: "); SetColors(Color.Red, Color.DefaultBackgroundColor); - Error.Write(_stats[4]); + Error.Write(_stats[4].Value); ResetColors(); Error.Write(", 5xx: "); SetColors(Color.Red, Color.DefaultBackgroundColor); - Error.Write(_stats[5]); + Error.Write(_stats[5].Value); ResetColors(); Error.Write(", others: "); SetColors(Color.Magenta, Color.DefaultBackgroundColor); - Error.Write(_stats[0]); + Error.Write(_stats[0].Value); ResetColors(); NewLine(OutputPipe.Error); // Reset location @@ -159,7 +159,7 @@ private void PrintInitialMetrics() { /// public PulseResult Consolidate() => new() { Results = _results, - SuccessRate = Math.Round((double)_stats[2] / _responses * 100, 2), + SuccessRate = Math.Round((double)_stats[2].Value / _responses.Value * 100, 2), TotalDuration = Stopwatch.GetElapsedTime(_start) }; } \ No newline at end of file diff --git a/src/Pulse/Core/PulseSummary.cs b/src/Pulse/Core/PulseSummary.cs index 2c042ea..ebb20ac 100644 --- a/src/Pulse/Core/PulseSummary.cs +++ b/src/Pulse/Core/PulseSummary.cs @@ -1,7 +1,6 @@ using System.Net; using static PrettyConsole.Console; using PrettyConsole; -using System.Runtime.CompilerServices; using Pulse.Configuration; using Sharpify; @@ -43,9 +42,8 @@ public sealed class PulseSummary { ? new HashSet(new ResponseComparer(Parameters)) : []; Dictionary statusCounter = []; - double minLatency = double.MaxValue, maxLatency = double.MinValue, avgLatency = 0; - double minSize = double.MaxValue, maxSize = double.MinValue, avgSize = 0; - double multiplier = 1 / (double)completed; + var latencies = new List(completed); + var sizes = new List(completed); long totalSize = 0; int peakConcurrentConnections = 0; @@ -60,15 +58,11 @@ public sealed class PulseSummary { totalSize += RequestSizeInBytes; // duration var latency = result.Latency.TotalMilliseconds; - minLatency = Math.Min(minLatency, latency); - maxLatency = Math.Max(maxLatency, latency); - avgLatency += multiplier * latency; + latencies.Add(latency); // size var size = result.ContentLength; if (size > 0) { - minSize = Math.Min(minSize, size); - maxSize = Math.Max(maxSize, size); - avgSize += multiplier * size; + sizes.Add(size); if (Parameters.Export) { totalSize += size; } @@ -77,15 +71,14 @@ public sealed class PulseSummary { var statusCode = result.StatusCode; statusCounter.GetValueRefOrAddDefault(statusCode, out _)++; } + var latencySummary = GetSummary(latencies.AsSpan()); + var sizeSummary = GetSummary(sizes.AsSpan()); + Func getSize = Utils.Strings.FormatBytes; + double throughput = totalSize / Result.TotalDuration.TotalSeconds; #if !DEBUG OverrideCurrentLine(["Cross referencing results...", " done!" * Color.Green], OutputPipe.Error); OverrideCurrentLine([]); #endif - maxSize = Math.Max(0, maxSize); - minSize = Math.Min(minSize, maxSize); - - Func getSize = Utils.Strings.FormatBytes; - double throughput = totalSize / Result.TotalDuration.TotalSeconds; if (Parameters.Verbose) { NewLine(OutputPipe.Error); @@ -93,12 +86,24 @@ public sealed class PulseSummary { ClearNextLines(3, OutputPipe.Out); } - WriteLine(["Request count: ", $"{completed}" * Color.Yellow]); - WriteLine(["Peak concurrent connections: ", $"{peakConcurrentConnections}" * Color.Yellow]); + static string Outliers(int n) => n is 1 ? "outlier" : "outliers"; + + WriteLine(["Request count: ", $"{completed}" * Color.Yellow]); + WriteLine(["Concurrent connections: ", $"{peakConcurrentConnections}" * Color.Yellow]); WriteLine(["Total duration: ", Utils.DateAndTime.FormatTimeSpan(Result.TotalDuration) * Color.Yellow]); WriteLine(["Success Rate: ", $"{Result.SuccessRate}%" * Helper.GetPercentageBasedColor(Result.SuccessRate)]); - WriteLine(["Latency: Min: ", $"{minLatency:0.##}ms" * Color.Cyan, ", Avg: ", $"{avgLatency:0.##}ms" * Color.Yellow, ", Max: ", $"{maxLatency:0.##}ms" * Color.Red]); - WriteLine(["Content Size: Min: ", getSize(minSize) * Color.Cyan, ", Avg: ", getSize(avgSize) * Color.Yellow, ", Max: ", getSize(maxSize) * Color.Red]); + Write(["Latency: Min: ", $"{latencySummary.Min:0.##}ms" * Color.Cyan, ", Avg: ", $"{latencySummary.Avg:0.##}ms" * Color.Yellow, ", Max: ", $"{latencySummary.Max:0.##}ms" * Color.Red]); + if (latencySummary.Removed is 0) { + NewLine(); + } else { + Out.WriteLine($" (Removed {latencySummary.Removed} {Outliers(latencySummary.Removed)})"); + } + Write(["Content Size: Min: ", getSize(sizeSummary.Min) * Color.Cyan, ", Avg: ", getSize(sizeSummary.Avg) * Color.Yellow, ", Max: ", getSize(sizeSummary.Max) * Color.Red]); + if (sizeSummary.Removed is 0) { + NewLine(); + } else { + Out.WriteLine($" (Removed {sizeSummary.Removed} {Outliers(sizeSummary.Removed)})"); + } WriteLine(["Total throughput: ", $"{getSize(throughput)}/s" * Color.Yellow]); Out.WriteLine("Status codes:"); foreach (var kvp in statusCounter) { @@ -125,7 +130,12 @@ public sealed class PulseSummary { double duration = result.Latency.TotalMilliseconds; var statusCode = result.StatusCode; - ClearNextLines(3, OutputPipe.Out); + if (Parameters.Verbose) { + NewLine(OutputPipe.Error); + } else { + ClearNextLines(3, OutputPipe.Out); + } + WriteLine(["Request count: ", "1" * Color.Yellow]); WriteLine(["Total duration: ", Utils.DateAndTime.FormatTimeSpan(Result.TotalDuration) * Color.Yellow]); if ((int)statusCode is >= 200 and < 300) { @@ -148,6 +158,68 @@ public sealed class PulseSummary { } } + /// + /// Creates an IQR summary from + /// + /// + /// + internal static Summary GetSummary(Span values) { + // if conditions ordered to promote default paths + if (values.Length > 2) { + values.Sort(); + + double q1 = values[values.Length / 4]; // First quartile + double q3 = values[3 * values.Length / 4]; // Third quartile + double iqr = q3 - q1; + + double lowerBound = q1 - 1.5 * iqr; + double upperBound = q3 + 1.5 * iqr; + + var summary = new Summary { + Min = double.MaxValue, + Max = double.MinValue, + Avg = 0 + }; + + var filtered = 0; + foreach (var value in values) { + if (value < lowerBound || value > upperBound) { + // Outside of IQR + summary.Removed++; + continue; + } + filtered++; + summary.Min = Math.Min(summary.Min, value); + summary.Max = Math.Max(summary.Max, value); + summary.Avg += value; + } + summary.Avg /= filtered; + + return summary; + } else if (values.Length is 2) { + return new Summary { + Min = Math.Min(values[0], values[1]), + Max = Math.Max(values[0], values[1]), + Avg = (values[0] + values[1]) / 2 + }; + } else if (values.Length is 1) { + return new Summary { + Min = values[0], + Max = values[0], + Avg = values[0] + }; + } else { + return new(); + } + } + + internal struct Summary { + public double Min; + public double Max; + public double Avg; + public int Removed; + } + /// /// Exports unique request results asynchronously and in parallel if possible /// diff --git a/src/Pulse/Core/SendCommand.cs b/src/Pulse/Core/SendCommand.cs index 0210957..6f689da 100644 --- a/src/Pulse/Core/SendCommand.cs +++ b/src/Pulse/Core/SendCommand.cs @@ -48,10 +48,12 @@ path to .json request details file get-sample : command - generates sample file get-schema : command - generates a json schema file check-for-updates: command - checks for updates + terms-of-use : print the terms of use --noop : print selected configuration but don't run -u, --url : override the url of the request -h, --help : print this help text - --terms-of-use : print the terms of use + Notes: + * when "-n" is 1, verbose output is enabled """; internal static ParametersBase ParseParametersArgs(Arguments args) { @@ -76,7 +78,7 @@ internal static ParametersBase ParseParametersArgs(Arguments args) { bool exportFullEquality = args.HasFlag("f"); bool disableExport = args.HasFlag("no-export"); bool noop = args.HasFlag("noop"); - bool verbose = args.HasFlag("v") || args.HasFlag("verbose"); + bool verbose = args.HasFlag("v") || args.HasFlag("verbose") || requests is 1; return new ParametersBase { Requests = requests, TimeoutInMs = timeoutInMs, @@ -112,21 +114,7 @@ internal static Result GetRequestDetails(string requestSource, A /// Executes the command /// /// - /// public override async ValueTask ExecuteAsync(Arguments args) { - if (args.HasFlag("terms-of-use")) { - Out.WriteLine( - """ - By using this tool you agree to take full responsibility for the consequences of its use. - - Usage of this tool for attacking targets without prior mutual consent is illegal. It is the end user's - responsibility to obey all applicable local, state and federal laws. - Developers assume no liability and are not responsible for any misuse or damage caused by this program. - """ - ); - return 0; - } - if (!args.TryGetValue(0, out string rf)) { WriteLine("request file or command name must be provided!" * Color.Red, OutputPipe.Error); return 1; @@ -160,7 +148,6 @@ public override async ValueTask ExecuteAsync(Arguments args) { WriteLine(Helper.CreateHeader(requestDetails.Request)); await Pulse.RunAsync(@params, requestDetails); - return 0; } @@ -209,6 +196,18 @@ public override async ValueTask ExecuteAsync(Arguments args) { } else { WriteLine("Failed to check for updates - server response was not success", OutputPipe.Error); } + }, + ["terms-of-use"] = _ => { + Out.WriteLine( + """ + By using this tool you agree to take full responsibility for the consequences of its use. + + Usage of this tool for attacking targets without prior mutual consent is illegal. It is the end user's + responsibility to obey all applicable local, state and federal laws. + Developers assume no liability and are not responsible for any misuse or damage caused by this program. + """ + ); + return ValueTask.CompletedTask; } }; diff --git a/src/Pulse/Core/VerbosePulseMonitor.cs b/src/Pulse/Core/VerbosePulseMonitor.cs index 4cf6dc6..2485b49 100644 --- a/src/Pulse/Core/VerbosePulseMonitor.cs +++ b/src/Pulse/Core/VerbosePulseMonitor.cs @@ -25,12 +25,12 @@ public sealed class VerbosePulseMonitor : IPulseMonitor { /// /// Current number of responses received /// - private volatile int _responses; + private PaddedULong _responses; /// /// Current number of successful responses received /// - private volatile int _successes; + private PaddedULong _successes; private readonly bool _saveContent; private readonly CancellationToken _cancellationToken; @@ -57,10 +57,10 @@ public VerbosePulseMonitor(HttpClient client, Request requestRecipe, Parameters public async Task SendAsync(int requestId) { PrintPreRequest(requestId); var result = await _requestExecutionContext.SendRequest(requestId, _requestRecipe, _httpClient, _saveContent, _cancellationToken); - Interlocked.Increment(ref _responses); + Interlocked.Increment(ref _responses.Value); // Increment stats if (result.StatusCode is HttpStatusCode.OK) { - Interlocked.Increment(ref _successes); + Interlocked.Increment(ref _successes.Value); } int status = (int)result.StatusCode; PrintPostRequest(requestId, status); @@ -69,7 +69,8 @@ public async Task SendAsync(int requestId) { private void PrintPreRequest(int requestId) { lock (_lock) { - Error.Write("Sending request id: "); + Write("--> ", OutputPipe.Error, Color.Yellow); + Error.Write("Sent request: "); SetColors(Color.Yellow, Color.DefaultBackgroundColor); Error.WriteLine(requestId); ResetColors(); @@ -78,7 +79,8 @@ private void PrintPreRequest(int requestId) { private void PrintPostRequest(int requestId, int statusCode) { lock (_lock) { - Error.Write("Received response id: "); + Write("<-- ", OutputPipe.Error, Color.Cyan); + Error.Write("Received response: "); SetColors(Color.Yellow, Color.DefaultBackgroundColor); Error.Write(requestId); ResetColors(); @@ -92,7 +94,7 @@ private void PrintPostRequest(int requestId, int statusCode) { /// public PulseResult Consolidate() => new() { Results = _results, - SuccessRate = Math.Round((double)_successes / _responses * 100, 2), + SuccessRate = Math.Round((double)_successes.Value / _responses.Value * 100, 2), TotalDuration = Stopwatch.GetElapsedTime(_start) }; } \ No newline at end of file diff --git a/src/Pulse/Program.cs b/src/Pulse/Program.cs index 2151b85..b6a3899 100644 --- a/src/Pulse/Program.cs +++ b/src/Pulse/Program.cs @@ -7,7 +7,7 @@ using PrettyConsole; internal class Program { - internal const string VERSION = "1.0.7.0"; + internal const string VERSION = "1.1.0.0"; private static async Task Main(string[] args) { using CancellationTokenSource globalCTS = new(); @@ -24,7 +24,7 @@ private static async Task Main(string[] args) { .UseConsoleAsOutputWriter() .WithMetadata(metadata => metadata.Version = VERSION) .WithCustomHeader( - $""" + """ Pulse - A hyper fast general purpose HTTP request tester Repository: https://github.com/dusrdev/Pulse diff --git a/src/Pulse/Pulse.csproj b/src/Pulse/Pulse.csproj index 5f15777..2064aae 100644 --- a/src/Pulse/Pulse.csproj +++ b/src/Pulse/Pulse.csproj @@ -15,7 +15,7 @@ true true false - 1.0.7.0 + 1.1.0.0 true https://github.com/dusrdev/Pulse git