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("");
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