Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Version 1.1.0.0 #14

Closed
wants to merge 19 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 0 additions & 13 deletions .idea/.idea.Pulse/.idea/.gitignore

This file was deleted.

4 changes: 0 additions & 4 deletions .idea/.idea.Pulse/.idea/encodings.xml

This file was deleted.

8 changes: 0 additions & 8 deletions .idea/.idea.Pulse/.idea/indexLayout.xml

This file was deleted.

6 changes: 0 additions & 6 deletions .idea/.idea.Pulse/.idea/vcs.xml

This file was deleted.

10 changes: 10 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
4 changes: 3 additions & 1 deletion Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
205 changes: 162 additions & 43 deletions src/Pulse/Core/Exporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 = $"<pre>{DefaultJsonContext.SerializeException(result.Exception)}</pre>";
}
Expand All @@ -49,83 +52,196 @@ public static async Task ExportHtmlAsync(Response result, string path, bool form
if (result.Headers.Any()) {
headers =
$"""
<div>
<div class="table-section">
{ToHtmlTable(result.Headers)}
</div>
""";
}
string body =
$$"""
<!DOCTYPE html>
<html lang="en">
<title>Response: {{result.Id}}</title>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1" charset="utf-8"/>
<style>
const string css =
"""
/* Reset and Base Styles */
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
display: flex;
flex-direction: column;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #f4f6f8;
color: #333;
min-height: 100vh;
padding: 20px;
font-size: small; /* Set font size to small */
}
html, body {
height: 98%;
/* Status Section Styles */
.status-section {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
background-color: #fff;
padding: 10px 20px;
border-top-left-radius: 8px;
border-top-right-radius: 8px;
border-bottom-left-radius: 8px; /* Rounded bottom corners */
border-bottom-right-radius: 8px;
margin-bottom: 20px; /* Margin between status and table sections */
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
h1 {
font-family: 'Lucida Bright';
font-size: 200%;
text-align: center;
.status-section h1.title {
margin-right: 20px; /* Space between h1 and h2 */
font-size: 2rem;
/* Gradient Text: Dark Purple, Blue, and Pink */
background: linear-gradient(to right, #6a0dad, #0000ff, #ff389b);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
color: transparent;
}
h2 {
text-align: left;
.status-section h1 {
margin-left: 20px; /* Space between h1 and h2 */
font-size: 2rem;
}
/* Section Styles */
.table-section, .iframe-section {
background-color: #fff;
border-radius: 8px;
margin-bottom: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
/* Section Headers */
.iframe-section {
padding: 10px;
overflow: visible;
}
.iframe-section h2 {
position: sticky;
top: 0;
background: #fff;
font-size: 1.75rem;
width: 100%;
padding: 5px;
margin-bottom: 15px;
color: #000;
border-bottom: 2px solid #6a0dad;
}
/* Table Styles */
table {
border-collapse: collapse;
table-layout: fixed;
width: 100%;
margin: 5px 0;
border-collapse: separate; /* Changed from collapse to separate to allow border-radius */
border-spacing: 0;
table-layout: fixed;
word-wrap: break-word;
}
table,td,th {
border: 1px solid;
col:first-child {
width: 33.33%;
}
tr:nth-child(even) {
background-color: whitesmoke;
col:nth-child(2) {
width: 66.66%;
}
th, td {
padding: 12px 15px;
text-align: left;
vertical-align: top;
}
thead {
background: #333;
color: floralwhite;
}
/* Colored Background for Table Headers */
th {
background-color: black;
color: white;
color: #fff;
font-weight: bold;
font-size: 1rem;
}
td,th {
padding: 8px;
text-align: left;
vertical-align: top;
overflow: auto;
/* Remove individual column background colors */
th.header, td.header,
th.value, td.value {
background-color: inherit;
color: inherit;
}
td.header, th.header {
width: 25%;
/* Apply uniform background to all data cells */
tbody td {
background-color: #f9f9f9;
color: #333;
}
td.value, th.value {
width: 75%;

tr:nth-child(even) td {
background-color: #f1f1f1;
}

tr td:last-child {
border-top-right-radius: 8px;
border-bottom-right-radius: 8px;
}

/* Hover Effect for Table Rows */
tr:hover td {
background-color: #e6f7ff;
}

/* Iframe Section Styles */
.iframe-container {
flex: 1;
flex-direction: row;
display: flex;
flex-direction: column;
}
iframe {
flex: 1;
width: 100%;
height: 1000px;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
height: 1000px; /* Increased height to 1000px */
border: none;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
background-color: #fff; /* Removed gray background */
padding: 10px;
}
/* Responsive Design */
@media (max-width: 768px) {
.status-section {
flex-direction: column;
align-items: center;
}
.status-section h1.title {
margin-right: 0;
margin-bottom: 10px;
font-size: 2rem;
}
.status-section h2 {
font-size: 1rem;
}
.iframe-section h2 {
font-size: 1.5rem;
}
th, td {
padding: 10px 12px;
}
iframe {
height: 600px; /* Adjusted height for smaller screens */
}
}
""";
string body =
$$"""
<!DOCTYPE html>
<html lang="en">
<title>Response: {{result.Id}}</title>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1" charset="utf-8"/>
<style>
{{css}}
</style>
</head>
<body>
<h1 class="title">Response: {{result.Id}}</h1>
<div>
<h2>StatusCode: {{statusCode}} ({{(int)statusCode}})</h2>
<div class="status-section">
<h1 class="title">Response: {{result.Id}}</h1>
<h1>{{status}}</h1>
</div>
{{headers}}
<div class="iframe-section">
{{contentFrame}}
</div>
</body>
""";
await File.WriteAllTextAsync(filename, body, token);
Expand All @@ -142,6 +258,9 @@ internal static string ToHtmlTable(IEnumerable<KeyValuePair<string, IEnumerable<

// Start the table and add some basic styling
sb.AppendLine("<table>");
sb.AppendLine("<colgroup>");
sb.AppendLine("<col><col>");
sb.AppendLine("</colgroup>");
sb.AppendLine("<thead>");
sb.AppendLine("<tr>");
sb.AppendLine("<th class=\"header\">Header</th>");
Expand Down
6 changes: 3 additions & 3 deletions src/Pulse/Core/IPulseMonitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public static IPulseMonitor Create(HttpClient client, Request requestRecipe, Par
/// Request execution context
/// </summary>
internal sealed class RequestExecutionContext {
private volatile int _currentConcurrentConnections;
private PaddedULong _currentConcurrentConnections;

/// <summary>
/// Sends a request
Expand All @@ -59,10 +59,10 @@ public async Task<Response> 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;
Expand Down
8 changes: 8 additions & 0 deletions src/Pulse/Core/PaddedULong.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System.Runtime.InteropServices;

namespace Pulse.Core;

[StructLayout(LayoutKind.Sequential, Size = 64)]
internal struct PaddedULong {
public ulong Value;
}
Loading