feat: Interface-Durchsatz-Chart — Backend berechnet RX/TX-Rate (bps), Frontend zeigt Dual-Sparkline per Klick auf Interface
This commit is contained in:
+39
-8
@@ -15,8 +15,15 @@ import (
|
||||
_ "github.com/jackc/pgx/v5/stdlib"
|
||||
)
|
||||
|
||||
type ifaceSnapshot struct {
|
||||
rxBytes float64
|
||||
txBytes float64
|
||||
ts time.Time
|
||||
}
|
||||
|
||||
type Database struct {
|
||||
db *sql.DB
|
||||
db *sql.DB
|
||||
lastIfaceSnap map[string]map[string]ifaceSnapshot // agentID -> ifaceName -> snapshot
|
||||
}
|
||||
|
||||
type DBConfig struct {
|
||||
@@ -49,7 +56,7 @@ func New(cfg DBConfig) (*Database, error) {
|
||||
return nil, fmt.Errorf("Datenbank-Verbindung fehlgeschlagen: %w", err)
|
||||
}
|
||||
|
||||
d := &Database{db: db}
|
||||
d := &Database{db: db, lastIfaceSnap: make(map[string]map[string]ifaceSnapshot)}
|
||||
if err := d.migrate(); err != nil {
|
||||
return nil, fmt.Errorf("Migration fehlgeschlagen: %w", err)
|
||||
}
|
||||
@@ -415,13 +422,32 @@ func (d *Database) writeMetrics(tx *sql.Tx, agentID string, ts time.Time, data *
|
||||
}
|
||||
}
|
||||
|
||||
// Network Interfaces
|
||||
// Network Interfaces — kumulierte Bytes + abgeleitete Rate (bytes/s)
|
||||
if _, ok := d.lastIfaceSnap[agentID]; !ok {
|
||||
d.lastIfaceSnap[agentID] = make(map[string]ifaceSnapshot)
|
||||
}
|
||||
for _, iface := range data.NetworkInterfaces {
|
||||
if iface.RxBytes > 0 || iface.TxBytes > 0 {
|
||||
tags, _ := json.Marshal(map[string]string{"interface": iface.Name})
|
||||
stmt.Exec(ts, agentID, "interface_rx_bytes", float64(iface.RxBytes), string(tags))
|
||||
stmt.Exec(ts, agentID, "interface_tx_bytes", float64(iface.TxBytes), string(tags))
|
||||
rx := float64(iface.RxBytes)
|
||||
tx := float64(iface.TxBytes)
|
||||
tags, _ := json.Marshal(map[string]string{"interface": iface.Name})
|
||||
tagsStr := string(tags)
|
||||
if rx > 0 || tx > 0 {
|
||||
stmt.Exec(ts, agentID, "interface_rx_bytes", rx, tagsStr)
|
||||
stmt.Exec(ts, agentID, "interface_tx_bytes", tx, tagsStr)
|
||||
}
|
||||
// Rate berechnen wenn vorheriger Snapshot vorhanden
|
||||
if prev, ok := d.lastIfaceSnap[agentID][iface.Name]; ok {
|
||||
elapsed := ts.Sub(prev.ts).Seconds()
|
||||
if elapsed > 0 && elapsed < 300 { // max 5 min Lücke ignorieren
|
||||
rxRate := (rx - prev.rxBytes) / elapsed
|
||||
txRate := (tx - prev.txBytes) / elapsed
|
||||
if rxRate >= 0 && txRate >= 0 { // negative Werte ignorieren (Reboot)
|
||||
stmt.Exec(ts, agentID, "interface_rx_bps", rxRate, tagsStr)
|
||||
stmt.Exec(ts, agentID, "interface_tx_bps", txRate, tagsStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
d.lastIfaceSnap[agentID][iface.Name] = ifaceSnapshot{rxBytes: rx, txBytes: tx, ts: ts}
|
||||
}
|
||||
|
||||
// PF States
|
||||
@@ -858,7 +884,7 @@ func (d *Database) DeleteConfigBackup(agentID string, backupID int64) (bool, err
|
||||
// --- Metrics Abfragen ---
|
||||
|
||||
// GetMetrics holt Metriken fuer einen Agent in einem Zeitraum
|
||||
func (d *Database) GetMetrics(agentID, metric string, from, to time.Time, limit int) ([]map[string]interface{}, error) {
|
||||
func (d *Database) GetMetrics(agentID, metric, tagsFilter string, from, to time.Time, limit int) ([]map[string]interface{}, error) {
|
||||
if limit <= 0 {
|
||||
limit = 1000
|
||||
}
|
||||
@@ -872,6 +898,11 @@ func (d *Database) GetMetrics(agentID, metric string, from, to time.Time, limit
|
||||
args = append(args, metric)
|
||||
}
|
||||
|
||||
if tagsFilter != "" {
|
||||
query += fmt.Sprintf(" AND tags @> $%d::jsonb", len(args)+1)
|
||||
args = append(args, tagsFilter)
|
||||
}
|
||||
|
||||
query += " ORDER BY time DESC LIMIT $" + fmt.Sprintf("%d", len(args)+1)
|
||||
args = append(args, limit)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user