feat(haproxy): req_tot + req_rate in Stats-API; Backend-Detail + Dashboard nutzen sie
GET /haproxy/stats liefert jetzt req_tot (kumulierte Requests seit HAProxy-Start) und req_rate (Requests/s im letzten Messfenster). Backend-Detail zeigt in der Live-Spalte 'N sess · X/s' wenn Traffic fließt; Dashboard-HAProxy-Card zeigt req/s ebenfalls an. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -32,14 +32,16 @@ const haproxyAdminSock = "/run/haproxy/admin.sock"
|
|||||||
// 'show stat' CSV. We only emit the fields the dashboard cares
|
// 'show stat' CSV. We only emit the fields the dashboard cares
|
||||||
// about — full CSV is ~80 columns of which 90% are noise here.
|
// about — full CSV is ~80 columns of which 90% are noise here.
|
||||||
type backendStat struct {
|
type backendStat struct {
|
||||||
Backend string `json:"backend"` // pxname
|
Backend string `json:"backend"` // pxname
|
||||||
Server string `json:"server"` // svname
|
Server string `json:"server"` // svname
|
||||||
Status string `json:"status"` // UP|DOWN|MAINT|...
|
Status string `json:"status"` // UP|DOWN|MAINT|...
|
||||||
Sessions int64 `json:"sessions"` // current sessions (scur)
|
Sessions int64 `json:"sessions"` // current sessions (scur)
|
||||||
BIn int64 `json:"bytes_in"`
|
BIn int64 `json:"bytes_in"`
|
||||||
BOut int64 `json:"bytes_out"`
|
BOut int64 `json:"bytes_out"`
|
||||||
LastChg int64 `json:"last_change_sec"` // seconds since last status change
|
ReqTot int64 `json:"req_tot"` // total requests since start (req_tot)
|
||||||
Health string `json:"health,omitempty"` // check_status (e.g. L7OK)
|
ReqRate int64 `json:"req_rate"` // requests/s last second (req_rate, server-level)
|
||||||
|
LastChg int64 `json:"last_change_sec"` // seconds since last status change
|
||||||
|
Health string `json:"health,omitempty"` // check_status (e.g. L7OK)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *HAProxyStatsHandler) Stats(c *gin.Context) {
|
func (h *HAProxyStatsHandler) Stats(c *gin.Context) {
|
||||||
@@ -97,6 +99,8 @@ func (h *HAProxyStatsHandler) Stats(c *gin.Context) {
|
|||||||
Sessions: parseInt64(safeAt(fields, colIdx["scur"])),
|
Sessions: parseInt64(safeAt(fields, colIdx["scur"])),
|
||||||
BIn: parseInt64(safeAt(fields, colIdx["bin"])),
|
BIn: parseInt64(safeAt(fields, colIdx["bin"])),
|
||||||
BOut: parseInt64(safeAt(fields, colIdx["bout"])),
|
BOut: parseInt64(safeAt(fields, colIdx["bout"])),
|
||||||
|
ReqTot: parseInt64(safeAt(fields, colIdx["req_tot"])),
|
||||||
|
ReqRate: parseInt64(safeAt(fields, colIdx["req_rate"])),
|
||||||
LastChg: parseInt64(safeAt(fields, colIdx["lastchg"])),
|
LastChg: parseInt64(safeAt(fields, colIdx["lastchg"])),
|
||||||
Health: safeAt(fields, colIdx["check_status"]),
|
Health: safeAt(fields, colIdx["check_status"]),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ async function listDomains(): Promise<DomainFull[]> {
|
|||||||
interface HAProxyStat {
|
interface HAProxyStat {
|
||||||
backend: string; server: string; status: string
|
backend: string; server: string; status: string
|
||||||
sessions: number; bytes_in: number; bytes_out: number
|
sessions: number; bytes_in: number; bytes_out: number
|
||||||
|
req_tot: number; req_rate: number
|
||||||
last_change_sec: number; health: string
|
last_change_sec: number; health: string
|
||||||
}
|
}
|
||||||
async function listHAProxyStats(): Promise<HAProxyStat[]> {
|
async function listHAProxyStats(): Promise<HAProxyStat[]> {
|
||||||
@@ -289,9 +290,11 @@ function ServerPanel({ backendID, haproxyStats }: { backendID: number; haproxySt
|
|||||||
if (!stat) return <Text type="secondary" style={{ fontSize: 11 }}>—</Text>
|
if (!stat) return <Text type="secondary" style={{ fontSize: 11 }}>—</Text>
|
||||||
const color = stat.status === 'UP' ? 'green' : stat.status === 'no check' ? 'default' : 'red'
|
const color = stat.status === 'UP' ? 'green' : stat.status === 'no check' ? 'default' : 'red'
|
||||||
return (
|
return (
|
||||||
<Tooltip title={`↓${fmtBytes(stat.bytes_in)} ↑${fmtBytes(stat.bytes_out)} · ${stat.health || stat.status}`}>
|
<Tooltip title={`↓${fmtBytes(stat.bytes_in)} ↑${fmtBytes(stat.bytes_out)} · ${stat.req_tot} req total · ${stat.health || stat.status}`}>
|
||||||
<Tag color={color} style={{ margin: 0, fontSize: 11 }}>{stat.status}</Tag>
|
<Tag color={color} style={{ margin: 0, fontSize: 11 }}>{stat.status}</Tag>
|
||||||
<Text type="secondary" style={{ fontSize: 11, marginLeft: 4 }}>{stat.sessions} sess</Text>
|
<Text type="secondary" style={{ fontSize: 11, marginLeft: 4 }}>
|
||||||
|
{stat.sessions} sess{stat.req_rate > 0 ? ` · ${stat.req_rate}/s` : ''}
|
||||||
|
</Text>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ interface AuditEntry {
|
|||||||
interface HAProxyBackend {
|
interface HAProxyBackend {
|
||||||
backend: string; server: string; status: string
|
backend: string; server: string; status: string
|
||||||
sessions: number; bytes_in: number; bytes_out: number
|
sessions: number; bytes_in: number; bytes_out: number
|
||||||
|
req_tot: number; req_rate: number
|
||||||
last_change_sec: number; health?: string
|
last_change_sec: number; health?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -407,7 +408,7 @@ export default function DashboardPage() {
|
|||||||
style={{ margin: 0, fontSize: 10 }}
|
style={{ margin: 0, fontSize: 10 }}
|
||||||
>{b.status}</Tag>
|
>{b.status}</Tag>
|
||||||
<Text type="secondary" style={{ fontSize: 11 }}>
|
<Text type="secondary" style={{ fontSize: 11 }}>
|
||||||
{b.sessions} sess · ↓{formatBytes(b.bytes_in)} ↑{formatBytes(b.bytes_out)}
|
{b.sessions} sess{b.req_rate > 0 ? ` · ${b.req_rate}/s` : ''} · ↓{formatBytes(b.bytes_in)} ↑{formatBytes(b.bytes_out)}
|
||||||
</Text>
|
</Text>
|
||||||
<Text type="secondary" style={{ fontSize: 11, marginLeft: 'auto' }}>{formatUptime(b.last_change_sec)}</Text>
|
<Text type="secondary" style={{ fontSize: 11, marginLeft: 'auto' }}>{formatUptime(b.last_change_sec)}</Text>
|
||||||
</Space>
|
</Space>
|
||||||
|
|||||||
Reference in New Issue
Block a user