diff --git a/agent/docker.go b/agent/docker.go index a5880f92..3862004f 100644 --- a/agent/docker.go +++ b/agent/docker.go @@ -28,8 +28,10 @@ import ( // ansiEscapePattern matches ANSI escape sequences (colors, cursor movement, etc.) // This includes CSI sequences like \x1b[...m and simple escapes like \x1b[K -var ansiEscapePattern = regexp.MustCompile(`\x1b\[[0-9;]*[a-zA-Z]|\x1b\][^\x07]*\x07|\x1b[@-Z\\-_]`) -var dockerContainerIDPattern = regexp.MustCompile(`^[a-fA-F0-9]{12,64}$`) +var ( + ansiEscapePattern = regexp.MustCompile(`\x1b\[[0-9;]*[a-zA-Z]|\x1b\][^\x07]*\x07|\x1b[@-Z\\-_]`) + dockerContainerIDPattern = regexp.MustCompile(`^[a-fA-F0-9]{12,64}$`) +) const ( // Docker API timeout in milliseconds diff --git a/agent/pve.go b/agent/pve.go index 74969608..004ba4f4 100644 --- a/agent/pve.go +++ b/agent/pve.go @@ -4,6 +4,7 @@ import ( "context" "crypto/tls" "errors" + "log/slog" "net/http" "time" @@ -22,24 +23,29 @@ type pveManager struct { // Returns stats for all running VMs/LXCs func (pm *pveManager) getPVEStats() ([]*container.Stats, error) { if pm.client == nil { + slog.Info("PVE client not configured") return nil, errors.New("PVE client not configured") } cluster, err := pm.client.Cluster(context.Background()) if err != nil { + slog.Error("Error getting cluster", "err", err) return nil, err } + slog.Info("PVE cluster", "cluster", cluster) resources, err := cluster.Resources(context.Background(), "vm") if err != nil { + slog.Error("Error getting resources", "err", err, "resources", resources) return nil, err } - + slog.Info("PVE resources", "resources", resources) containersLength := len(resources) - + slog.Info("PVE containers length", "containersLength", containersLength) containerIds := make(map[string]struct{}, containersLength) // only include running vms and lxcs on selected node for _, resource := range resources { if resource.Node == pm.nodeName && resource.Status == "running" { + slog.Info("PVE resource", "resource", resource) containerIds[resource.ID] = struct{}{} } } @@ -53,6 +59,7 @@ func (pm *pveManager) getPVEStats() ([]*container.Stats, error) { // populate stats stats := make([]*container.Stats, 0, len(containerIds)) for _, resource := range resources { + // slog.Info("PVE resource", "resource", resource) if _, exists := containerIds[resource.ID]; !exists { continue } @@ -71,6 +78,10 @@ func (pm *pveManager) getPVEStats() ([]*container.Stats, error) { resourceStats.Id = resource.ID // Store type (e.g. "qemu" or "lxc") in .Image (cbor key 8, json:"-") resourceStats.Image = resource.Type + // PVE limits (cbor-only, for pve_vms table) + resourceStats.MaxCPU = resource.MaxCPU + resourceStats.MaxMem = resource.MaxMem + resourceStats.Uptime = resource.Uptime // prevent first run from sending all prev sent/recv bytes total_sent := uint64(resource.NetOut) total_recv := uint64(resource.NetIn) @@ -87,14 +98,18 @@ func (pm *pveManager) getPVEStats() ([]*container.Stats, error) { resourceStats.Mem = float64(resource.Mem) resourceStats.Bandwidth = [2]uint64{uint64(sent_delta), uint64(recv_delta)} + slog.Info("PVE resource stats", "resourceStats", resourceStats) + stats = append(stats, resourceStats) } + slog.Info("PVE stats", "stats", stats) return stats, nil } // Creates a new PVE manager func newPVEManager(_ *Agent) *pveManager { + slog.Info("Creating new PVE manager") url, exists := GetEnv("PROXMOX_URL") if !exists { url = "https://localhost:8006/api2/json" @@ -103,6 +118,11 @@ func newPVEManager(_ *Agent) *pveManager { tokenID, tokenIDExists := GetEnv("PROXMOX_TOKENID") secret, secretExists := GetEnv("PROXMOX_SECRET") + slog.Info("PROXMOX_URL", "url", url) + slog.Info("PROXMOX_NODE", "nodeName", nodeName) + slog.Info("PROXMOX_TOKENID", "tokenID", tokenID) + slog.Info("PROXMOX_SECRET", "secret", secret) + // PROXMOX_INSECURE_TLS defaults to true; set to "false" to enable TLS verification insecureTLS := true if val, exists := GetEnv("PROXMOX_INSECURE_TLS"); exists { @@ -123,6 +143,7 @@ func newPVEManager(_ *Agent) *pveManager { proxmox.WithAPIToken(tokenID, secret), ) } else { + slog.Error("Env variables not set") client = nil } @@ -133,13 +154,15 @@ func newPVEManager(_ *Agent) *pveManager { } // Retrieve node cpu count if client != nil { + slog.Info("Getting node CPU count", "nodeName", nodeName) node, err := client.Node(context.Background(), nodeName) if err != nil { pveManager.client = nil + slog.Error("Error getting node", "err", err) } else { pveManager.cpuCount = node.CPUInfo.CPUs + slog.Info("Node CPU count", "cpuCount", pveManager.cpuCount) } } - return pveManager } diff --git a/internal/entities/container/container.go b/internal/entities/container/container.go index a0d96c71..7786e524 100644 --- a/internal/entities/container/container.go +++ b/internal/entities/container/container.go @@ -140,6 +140,9 @@ type Stats struct { Status string `json:"-" cbor:"6,keyasint"` Id string `json:"-" cbor:"7,keyasint"` Image string `json:"-" cbor:"8,keyasint"` + MaxCPU uint64 `json:"-" cbor:"10,keyasint,omitzero"` // PVE: max vCPU count + MaxMem uint64 `json:"-" cbor:"11,keyasint,omitzero"` // PVE: max memory bytes + Uptime uint64 `json:"-" cbor:"12,keyasint,omitzero"` // PVE: uptime in seconds // PrevCpu [2]uint64 `json:"-"` CpuSystem uint64 `json:"-"` CpuContainer uint64 `json:"-"` diff --git a/internal/hub/systems/system.go b/internal/hub/systems/system.go index c8ece9c6..8d4a6d1e 100644 --- a/internal/hub/systems/system.go +++ b/internal/hub/systems/system.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "hash/fnv" + "log/slog" "math/rand" "net" "strings" @@ -213,6 +214,7 @@ func (sys *System) createRecords(data *system.CombinedData) (*core.Record, error if len(data.PVEStats) > 0 { if data.PVEStats[0].Id != "" { if err := createPVEVMRecords(txApp, data.PVEStats, sys.Id); err != nil { + slog.Error("Error creating PVE VM records", "err", err) return err } } @@ -225,6 +227,7 @@ func (sys *System) createRecords(data *system.CombinedData) (*core.Record, error pveStatsRecord.Set("stats", data.PVEStats) pveStatsRecord.Set("type", "1m") if err := txApp.SaveNoValidate(pveStatsRecord); err != nil { + slog.Error("Error creating PVE stats records", "err", err) return err } } @@ -364,20 +367,20 @@ func createPVEVMRecords(app core.App, data []*container.Stats, systemId string) valueStrings := make([]string, 0, len(data)) for i, vm := range data { suffix := fmt.Sprintf("%d", i) - valueStrings = append(valueStrings, fmt.Sprintf("({:id%[1]s}, {:system}, {:name%[1]s}, {:type%[1]s}, {:cpu%[1]s}, {:memory%[1]s}, {:net%[1]s}, {:updated})", suffix)) + valueStrings = append(valueStrings, fmt.Sprintf("({:id%[1]s}, {:system}, {:name%[1]s}, {:type%[1]s}, {:cpu%[1]s}, {:mem%[1]s}, {:net%[1]s}, {:maxcpu%[1]s}, {:maxmem%[1]s}, {:uptime%[1]s}, {:updated})", suffix)) params["id"+suffix] = makeStableHashId(systemId, vm.Id) params["name"+suffix] = vm.Name params["type"+suffix] = vm.Image // "qemu" or "lxc" params["cpu"+suffix] = vm.Cpu - params["memory"+suffix] = vm.Mem + params["mem"+suffix] = vm.Mem + params["maxcpu"+suffix] = vm.MaxCPU + params["maxmem"+suffix] = vm.MaxMem + params["uptime"+suffix] = vm.Uptime netBytes := vm.Bandwidth[0] + vm.Bandwidth[1] - if netBytes == 0 { - netBytes = uint64((vm.NetworkSent + vm.NetworkRecv) * 1024 * 1024) - } params["net"+suffix] = netBytes } queryString := fmt.Sprintf( - "INSERT INTO pve_vms (id, system, name, type, cpu, memory, net, updated) VALUES %s ON CONFLICT(id) DO UPDATE SET system=excluded.system, name=excluded.name, type=excluded.type, cpu=excluded.cpu, memory=excluded.memory, net=excluded.net, updated=excluded.updated", + "INSERT INTO pve_vms (id, system, name, type, cpu, mem, net, maxcpu, maxmem, uptime, updated) VALUES %s ON CONFLICT(id) DO UPDATE SET system=excluded.system, name=excluded.name, type=excluded.type, cpu=excluded.cpu, mem=excluded.mem, net=excluded.net, maxcpu=excluded.maxcpu, maxmem=excluded.maxmem, uptime=excluded.uptime, updated=excluded.updated", strings.Join(valueStrings, ","), ) _, err := app.DB().NewQuery(queryString).Bind(params).Execute() diff --git a/internal/migrations/0_collections_snapshot_0_19_0_dev_1.go b/internal/migrations/0_collections_snapshot_0_19_0_dev_2.go similarity index 97% rename from internal/migrations/0_collections_snapshot_0_19_0_dev_1.go rename to internal/migrations/0_collections_snapshot_0_19_0_dev_2.go index 4cf44c82..1d0db753 100644 --- a/internal/migrations/0_collections_snapshot_0_19_0_dev_1.go +++ b/internal/migrations/0_collections_snapshot_0_19_0_dev_2.go @@ -1691,11 +1691,11 @@ func init() { "deleteRule": null, "fields": [ { - "autogeneratePattern": "[a-z0-9]{15}", + "autogeneratePattern": "[a-z0-9]{10}", "hidden": false, "id": "text3208210256", - "max": 15, - "min": 15, + "max": 10, + "min": 10, "name": "id", "pattern": "^[a-z0-9]+$", "presentable": false, @@ -1765,11 +1765,11 @@ func init() { "type": "autodate" } ], - "id": "pve_stats_col001", + "id": "pvestats", "indexes": [ "CREATE INDEX ` + "`" + `idx_pve_stats_sys_type_created` + "`" + ` ON ` + "`" + `pve_stats` + "`" + ` (\n ` + "`" + `system` + "`" + `,\n ` + "`" + `type` + "`" + `,\n ` + "`" + `created` + "`" + `\n)" ], - "listRule": "@request.auth.id != \"\"", + "listRule": "@request.auth.id != \"\" && system.users.id ?= @request.auth.id", "name": "pve_stats", "system": false, "type": "base", @@ -1852,7 +1852,7 @@ func init() { "id": "pve_vms_mem001", "max": null, "min": 0, - "name": "memory", + "name": "mem", "onlyInt": false, "presentable": false, "required": false, @@ -1871,6 +1871,42 @@ func init() { "system": false, "type": "number" }, + { + "hidden": false, + "id": "number1253106325", + "max": null, + "min": null, + "name": "maxcpu", + "onlyInt": false, + "presentable": false, + "required": false, + "system": false, + "type": "number" + }, + { + "hidden": false, + "id": "number1693954525", + "max": null, + "min": null, + "name": "maxmem", + "onlyInt": false, + "presentable": false, + "required": false, + "system": false, + "type": "number" + }, + { + "hidden": false, + "id": "number1563400775", + "max": null, + "min": null, + "name": "uptime", + "onlyInt": false, + "presentable": false, + "required": false, + "system": false, + "type": "number" + }, { "hidden": false, "id": "pve_vms_upd001", @@ -1884,7 +1920,7 @@ func init() { "type": "number" } ], - "id": "pve_vms_col0001", + "id": "pvevms", "indexes": [ "CREATE INDEX ` + "`" + `idx_pve_vms_updated` + "`" + ` ON ` + "`" + `pve_vms` + "`" + ` (` + "`" + `updated` + "`" + `)", "CREATE INDEX ` + "`" + `idx_pve_vms_system` + "`" + ` ON ` + "`" + `pve_vms` + "`" + ` (` + "`" + `system` + "`" + `)" @@ -1896,6 +1932,7 @@ func init() { "updateRule": null, "viewRule": null } + ]` err := app.ImportCollectionsByMarshaledJSON([]byte(jsonData), false)