feat(traefik): request chart shows the total, a time axis and a weighted 5xx share; says why if it can't draw (#5256)
#5256 "Traefik — Request chart: blank on the live dashboard; time axis and honest labels". - "Req/s" was the average of the routers, not the total. Each scrape is now summed over routers, then averaged per bucket (request_history). - The 5xx share is now weighted by each router's traffic. The axis is no longer pinned at 0–100 (suggestedMax 5). - The x axis shows times, in the viewer's local time. The tooltip shows both series at that time. - A verdict line comes first: now / peak req/s and the peak 5xx share. The figures read even if the chart doesn't. - The blank live chart's cause is still unknown. Chart.js and chart-colors.js both load on the dashboard. A missing library or a thrown error is now written into the widget instead of leaving it empty, so the cause will show on the page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,74 +1,92 @@
|
||||
{# traefik/widget_request_chart.html — Chart.js request rate + error % over time #}
|
||||
{# traefik/widget_request_chart.html — requests per second across all routers
|
||||
and the share answered 5xx, over the last few hours (#5256). The verdict
|
||||
carries the figures, so the widget still says something if the chart
|
||||
can't draw; a chart that fails says why in place of the canvas. #}
|
||||
{% if not labels_json or labels_json == '[]' %}
|
||||
<div style="color:var(--text-muted);font-size:0.85rem;padding:0.5rem 0;">No data for last {{ hours }}h.</div>
|
||||
<div class="w-verdict" style="color:var(--text-muted);">No Traefik requests recorded in the last {{ hours }}h.</div>
|
||||
{% else %}
|
||||
<div style="position:relative;height:160px;">
|
||||
<canvas id="chart-traefik-req-{{ widget_id }}"></canvas>
|
||||
<div class="w-verdict {{ 'tone-crit' if peak_err and peak_err >= 1 else ('tone-warn' if peak_err else 'tone-ok') }}">
|
||||
Now {{ "%.2f"|format(now_req) }} req/s across all routers, peak {{ "%.2f"|format(peak_req) }}.
|
||||
{%- if peak_err %} 5xx peaked at {{ "%.2f"|format(peak_err) }}% of responses.{% else %} No 5xx.{% endif %}
|
||||
</div>
|
||||
<div style="position:relative;height:160px;" id="chart-traefik-req-box-{{ widget_id }}">
|
||||
<canvas id="chart-traefik-req-{{ widget_id }}" aria-label="Requests per second and 5xx share, last {{ hours }} hours"></canvas>
|
||||
</div>
|
||||
<script>
|
||||
(function() {
|
||||
var box = document.getElementById("chart-traefik-req-box-{{ widget_id }}");
|
||||
var ctx = document.getElementById("chart-traefik-req-{{ widget_id }}");
|
||||
if (!ctx || !window.Chart) return;
|
||||
var existing = Chart.getChart(ctx);
|
||||
if (existing) existing.destroy();
|
||||
new Chart(ctx.getContext("2d"), {
|
||||
type: "line",
|
||||
data: {
|
||||
labels: {{ labels_json | safe }},
|
||||
datasets: [
|
||||
{
|
||||
label: "Req/s",
|
||||
data: {{ req_rate_json | safe }},
|
||||
borderColor: stewardColor("--blue"),
|
||||
backgroundColor: stewardColor("--blue-dim"),
|
||||
fill: true,
|
||||
tension: 0.3,
|
||||
pointRadius: 0,
|
||||
borderWidth: 1.5,
|
||||
yAxisID: "y",
|
||||
},
|
||||
{
|
||||
label: "5xx %",
|
||||
data: {{ error_rate_json | safe }},
|
||||
borderColor: stewardColor("--red"),
|
||||
fill: false,
|
||||
tension: 0.3,
|
||||
pointRadius: 0,
|
||||
borderWidth: 1.5,
|
||||
yAxisID: "y2",
|
||||
},
|
||||
]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
animation: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
labels: { boxWidth: 10, font: { size: 10 } }
|
||||
}
|
||||
if (!ctx) return;
|
||||
function fail(why) {
|
||||
box.style.height = "auto";
|
||||
box.innerHTML = '<div class="w-note"></div>';
|
||||
box.firstChild.textContent = "The chart couldn't be drawn: " + why;
|
||||
}
|
||||
if (!window.Chart) { fail("the chart library didn't load."); return; }
|
||||
try {
|
||||
var existing = Chart.getChart(ctx);
|
||||
if (existing) existing.destroy();
|
||||
var times = {{ labels_json | safe }};
|
||||
var hhmm = function (ms) {
|
||||
return new Date(ms).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
|
||||
};
|
||||
new Chart(ctx.getContext("2d"), {
|
||||
type: "line",
|
||||
data: {
|
||||
labels: times,
|
||||
datasets: [
|
||||
{
|
||||
label: "Requests/s (all routers)",
|
||||
data: {{ req_rate_json | safe }},
|
||||
borderColor: stewardColor("--blue"),
|
||||
backgroundColor: stewardColor("--blue-dim"),
|
||||
fill: true, tension: 0.3, pointRadius: 0, borderWidth: 1.5, yAxisID: "y",
|
||||
},
|
||||
{
|
||||
label: "5xx share %",
|
||||
data: {{ error_rate_json | safe }},
|
||||
borderColor: stewardColor("--red"),
|
||||
fill: false, tension: 0.3, pointRadius: 0, borderWidth: 1.5, yAxisID: "y2",
|
||||
},
|
||||
]
|
||||
},
|
||||
scales: {
|
||||
x: { display: false },
|
||||
y: {
|
||||
min: 0,
|
||||
ticks: { font: { size: 10 } },
|
||||
title: { display: true, text: "req/s", font: { size: 9 } },
|
||||
},
|
||||
y2: {
|
||||
position: "right",
|
||||
min: 0,
|
||||
max: 100,
|
||||
grid: { drawOnChartArea: false },
|
||||
ticks: { color: stewardColor("--red"), font: { size: 10 } },
|
||||
title: { display: true, text: "5xx%", color: stewardColor("--red"), font: { size: 9 } },
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
animation: false,
|
||||
interaction: { mode: "index", intersect: false },
|
||||
plugins: {
|
||||
legend: { labels: { boxWidth: 10, font: { size: 10 } } },
|
||||
tooltip: { callbacks: { title: function (items) { return items.length ? hhmm(times[items[0].dataIndex]) : ""; } } },
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
ticks: {
|
||||
font: { size: 10 }, maxRotation: 0, autoSkip: true, maxTicksLimit: 6,
|
||||
callback: function (value) { return hhmm(times[value]); },
|
||||
},
|
||||
grid: { display: false },
|
||||
},
|
||||
y: {
|
||||
min: 0,
|
||||
ticks: { font: { size: 10 } },
|
||||
title: { display: true, text: "req/s", font: { size: 9 } },
|
||||
},
|
||||
y2: {
|
||||
position: "right",
|
||||
min: 0,
|
||||
suggestedMax: 5,
|
||||
grid: { drawOnChartArea: false },
|
||||
ticks: { color: stewardColor("--red"), font: { size: 10 } },
|
||||
title: { display: true, text: "5xx %", color: stewardColor("--red"), font: { size: 9 } },
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
fail(err && err.message ? err.message : String(err));
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<div style="font-size:0.72rem;color:var(--text-muted);margin-top:0.35rem;text-align:right;">
|
||||
last {{ hours }}h
|
||||
</div>
|
||||
<div class="w-note" style="text-align:right;">last {{ hours }}h, your local time</div>
|
||||
{% endif %}
|
||||
|
||||
Reference in New Issue
Block a user