Skip to content
Merged
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
85 changes: 84 additions & 1 deletion DebugProbe.AspNetCore/Assets/css/debugprobe.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* =========================
/* =========================
Base
========================= */

Expand Down Expand Up @@ -1128,3 +1128,86 @@ pre {
background: #4a1717;
color: #ff8a8a !important;
}

/* =========================
Waterfall View
========================= */

.waterfall-container {
margin-bottom: 12px;
}

.waterfall-container .trace-card-main {
padding: 12px 14px;
}

.waterfall-container .trace-card-header {
padding: 0 0 12px 0;
}

.waterfall-container .trace-dot {
background: #9b51e0;
}

.waterfall-row {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 8px;
}

.waterfall-row:last-child {
margin-bottom: 0;
}

.wf-label {
flex: 0 0 200px;
width: 200px;
min-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-family: ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", monospace;
font-size: 12px;
color: #4b5563;
}

.wf-track {
flex: 1;
position: relative;
height: 20px;
background: #f3f4f6;
border-radius: 4px;
}

.wf-bar {
position: absolute;
top: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
background: #9b51e0;
color: #fff;
font-size: 10px;
font-weight: bold;
border-radius: 3px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding: 0 4px;
box-sizing: border-box;
}

.wf-bar--error {
background: #e74c3c;
}

@media (max-width: 640px) {
.wf-label {
flex: 0 0 100px;
width: 100px;
min-width: 100px;
}
}

69 changes: 67 additions & 2 deletions DebugProbe.AspNetCore/Internal/Rendering/HtmlRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,12 @@ public static string RenderDetailsPage(DebugEntry x, DebugEnvironment e, string
BuildPayloadSection("Body", res, "body")
]);

var waterfall = BuildWaterfallSection(x);

var outgoingRequests = string.Join("", x.OutgoingRequests.Select(BuildOutgoingRequestCard));

var combinedOutgoing = waterfall + outgoingRequests;

var content = EmbeddedResources.Details
.Replace("{{method}}", Encode(x.Method))
.Replace("{{path}}", Encode(pathWithQuery))
Expand All @@ -124,9 +128,9 @@ public static string RenderDetailsPage(DebugEntry x, DebugEnvironment e, string
.Replace("{{dateFormat}}", e.DateFormat ?? "")
.Replace("{{assemblyVersion}}", Encode(e.AssemblyVersion))
.Replace("{{outgoingRequests}}",
string.IsNullOrWhiteSpace(outgoingRequests)
string.IsNullOrWhiteSpace(combinedOutgoing)
? "<div class='empty-state trace-empty'>No outgoing dependency calls</div>"
: outgoingRequests)
: combinedOutgoing)
.Replace("{{incomingRequest}}", incomingRequest)
.Replace("{{incomingResponse}}", incomingResponse);

Expand Down Expand Up @@ -188,6 +192,67 @@ private static string BuildOutgoingRequestCard(DebugOutgoingRequest request)
details: details);
}

private static string BuildWaterfallSection(DebugEntry entry)
{
const double MinPercent = 0.0;
const double MaxPercent = 100.0;

if (entry.OutgoingRequests.Count == 0)
{
return string.Empty;
}

var totalSpan = (double)entry.DurationMs;
if (totalSpan <= 0)
{
totalSpan = 1.0;
}

var rowsHtml = new List<string>();

foreach (var outgoing in entry.OutgoingRequests)
{
var startOffsetMs = (outgoing.TimestampUtc - entry.Timestamp.UtcDateTime).TotalMilliseconds - outgoing.DurationMs;

var left = Math.Clamp((startOffsetMs / totalSpan) * MaxPercent, MinPercent, MaxPercent);
var width = Math.Clamp(((double)outgoing.DurationMs / totalSpan) * MaxPercent, MinPercent, MaxPercent);

var leftStr = left.ToString("0.##", System.Globalization.CultureInfo.InvariantCulture);
var widthStr = width.ToString("0.##", System.Globalization.CultureInfo.InvariantCulture);

var barClass = "wf-bar";
if (!outgoing.IsSuccessStatusCode || !string.IsNullOrWhiteSpace(outgoing.Exception))
{
barClass += " wf-bar--error";
}

var displayLabel = GetDisplayTarget(outgoing.Url);

rowsHtml.Add($@"
<div class=""waterfall-row"">
<span class=""wf-label"" title=""{Encode(outgoing.Url)}"">{Encode(displayLabel)}</span>
<div class=""wf-track"">
<div class=""{barClass}"" style=""left: {leftStr}%; width: {widthStr}%;"">{outgoing.DurationMs} ms</div>
</div>
</div>");
}

return $@"
<article class=""trace-card waterfall-container"">
<div class=""trace-card-main"">
<div class=""trace-card-header"">
<div class=""trace-card-title"">
<span class=""trace-dot"" aria-hidden=""true""></span>
<span class=""trace-label"">Waterfall Timeline</span>
</div>
</div>
<div class=""trace-details"">
{string.Join("", rowsHtml)}
</div>
</div>
</article>";
}

private static string BuildTraceCard(string label, string method, string target, string classes, IEnumerable<string> details, int? statusCode = null, string? statusText = null, long? durationMs = null)
{
var targetHost = GetDisplayTarget(target);
Expand Down
Loading