Compare commits

...

2 Commits

Author SHA1 Message Date
henrygd
330d375997 change to atomic.bool for fetching details / smart 2025-12-18 15:02:59 -05:00
henrygd
8627e3ee97 updates 2025-12-18 12:34:11 -05:00
3 changed files with 83 additions and 73 deletions

View File

@@ -6,11 +6,10 @@ import (
"errors" "errors"
"fmt" "fmt"
"hash/fnv" "hash/fnv"
"log/slog"
"math/rand" "math/rand"
"net" "net"
"strings" "strings"
"sync" "sync/atomic"
"time" "time"
"github.com/henrygd/beszel/internal/common" "github.com/henrygd/beszel/internal/common"
@@ -42,8 +41,9 @@ type System struct {
WsConn *ws.WsConn // Handler for agent WebSocket connection WsConn *ws.WsConn // Handler for agent WebSocket connection
agentVersion semver.Version // Agent version agentVersion semver.Version // Agent version
updateTicker *time.Ticker // Ticker for updating the system updateTicker *time.Ticker // Ticker for updating the system
smartOnce sync.Once // Once for fetching and saving smart devices detailsFetched atomic.Bool // True if static system details have been fetched and saved
detailsOnce sync.Once // Once for fetching and saving static system details smartFetched atomic.Bool // True if SMART devices have been fetched and saved
smartFetching atomic.Bool // True if SMART devices are currently being fetched
} }
func (sm *SystemManager) NewSystem(systemId string) *System { func (sm *SystemManager) NewSystem(systemId string) *System {
@@ -119,10 +119,10 @@ func (sys *System) update() error {
options := common.DataRequestOptions{ options := common.DataRequestOptions{
CacheTimeMs: uint16(interval), CacheTimeMs: uint16(interval),
} }
// fetch system details only on the first update // fetch system details if not already fetched
sys.detailsOnce.Do(func() { if !sys.detailsFetched.Load() {
options.IncludeDetails = true options.IncludeDetails = true
}) }
data, err := sys.fetchDataFromAgent(options) data, err := sys.fetchDataFromAgent(options)
if err == nil { if err == nil {
_, err = sys.createRecords(data) _, err = sys.createRecords(data)
@@ -151,18 +151,11 @@ func (sys *System) createRecords(data *system.CombinedData) (*core.Record, error
} }
hub := sys.manager.hub hub := sys.manager.hub
err = hub.RunInTransaction(func(txApp core.App) error { err = hub.RunInTransaction(func(txApp core.App) error {
if data.Details != nil { // add system_stats record
slog.Info("Static info", "data", data.Details)
if err := createStaticInfoRecord(txApp, data.Details, sys.Id); err != nil {
return err
}
}
// add system_stats and container_stats records
systemStatsCollection, err := txApp.FindCachedCollectionByNameOrId("system_stats") systemStatsCollection, err := txApp.FindCachedCollectionByNameOrId("system_stats")
if err != nil { if err != nil {
return err return err
} }
systemStatsRecord := core.NewRecord(systemStatsCollection) systemStatsRecord := core.NewRecord(systemStatsCollection)
systemStatsRecord.Set("system", systemRecord.Id) systemStatsRecord.Set("system", systemRecord.Id)
systemStatsRecord.Set("stats", data.Stats) systemStatsRecord.Set("stats", data.Stats)
@@ -170,14 +163,14 @@ func (sys *System) createRecords(data *system.CombinedData) (*core.Record, error
if err := txApp.SaveNoValidate(systemStatsRecord); err != nil { if err := txApp.SaveNoValidate(systemStatsRecord); err != nil {
return err return err
} }
// add containers and container_stats records
if len(data.Containers) > 0 { if len(data.Containers) > 0 {
// add / update containers records
if data.Containers[0].Id != "" { if data.Containers[0].Id != "" {
if err := createContainerRecords(txApp, data.Containers, sys.Id); err != nil { if err := createContainerRecords(txApp, data.Containers, sys.Id); err != nil {
return err return err
} }
} }
// add new container_stats record
containerStatsCollection, err := txApp.FindCachedCollectionByNameOrId("container_stats") containerStatsCollection, err := txApp.FindCachedCollectionByNameOrId("container_stats")
if err != nil { if err != nil {
return err return err
@@ -198,9 +191,16 @@ func (sys *System) createRecords(data *system.CombinedData) (*core.Record, error
} }
} }
// add system details record
if data.Details != nil {
if err := createSystemDetailsRecord(txApp, data.Details, sys.Id); err != nil {
return err
}
sys.detailsFetched.Store(true)
}
// update system record (do this last because it triggers alerts and we need above records to be inserted first) // update system record (do this last because it triggers alerts and we need above records to be inserted first)
systemRecord.Set("status", up) systemRecord.Set("status", up)
systemRecord.Set("info", data.Info) systemRecord.Set("info", data.Info)
if err := txApp.SaveNoValidate(systemRecord); err != nil { if err := txApp.SaveNoValidate(systemRecord); err != nil {
return err return err
@@ -210,36 +210,42 @@ func (sys *System) createRecords(data *system.CombinedData) (*core.Record, error
// Fetch and save SMART devices when system first comes online // Fetch and save SMART devices when system first comes online
if err == nil { if err == nil {
sys.smartOnce.Do(func() { if !sys.smartFetched.Load() && sys.smartFetching.CompareAndSwap(false, true) {
go sys.FetchAndSaveSmartDevices() go func() {
}) defer sys.smartFetching.Store(false)
if err := sys.FetchAndSaveSmartDevices(); err == nil {
sys.smartFetched.Store(true)
}
}()
}
} }
return systemRecord, err return systemRecord, err
} }
func createStaticInfoRecord(app core.App, data *system.Details, systemId string) error { func createSystemDetailsRecord(app core.App, data *system.Details, systemId string) error {
record, err := app.FindRecordById("system_details", systemId) collectionName := "system_details"
if err != nil { params := dbx.Params{
collection, err := app.FindCollectionByNameOrId("system_details") "id": systemId,
if err != nil { "system": systemId,
"hostname": data.Hostname,
"kernel": data.Kernel,
"cores": data.Cores,
"threads": data.Threads,
"cpu": data.CpuModel,
"os": data.Os,
"os_name": data.OsName,
"arch": data.Arch,
"memory": data.MemoryTotal,
"podman": data.Podman,
"updated": time.Now().UTC(),
}
result, err := app.DB().Update(collectionName, params, dbx.HashExp{"id": systemId}).Execute()
rowsAffected, _ := result.RowsAffected()
if err != nil || rowsAffected == 0 {
_, err = app.DB().Insert(collectionName, params).Execute()
}
return err return err
}
record = core.NewRecord(collection)
record.Set("id", systemId)
}
record.Set("system", systemId)
record.Set("hostname", data.Hostname)
record.Set("kernel", data.Kernel)
record.Set("cores", data.Cores)
record.Set("threads", data.Threads)
record.Set("cpu", data.CpuModel)
record.Set("os", data.Os)
record.Set("os_name", data.OsName)
record.Set("arch", data.Arch)
record.Set("memory", data.MemoryTotal)
record.Set("podman", data.Podman)
return app.SaveNoValidate(record)
} }
func createSystemdStatsRecords(app core.App, data []*systemd.Service, systemId string) error { func createSystemdStatsRecords(app core.App, data []*systemd.Service, systemId string) error {

View File

@@ -33,10 +33,7 @@
"noUnusedFunctionParameters": "error", "noUnusedFunctionParameters": "error",
"noUnusedPrivateClassMembers": "error", "noUnusedPrivateClassMembers": "error",
"useExhaustiveDependencies": { "useExhaustiveDependencies": {
"level": "warn", "level": "off"
"options": {
"reportUnnecessaryDependencies": false
}
}, },
"useUniqueElementIds": "off", "useUniqueElementIds": "off",
"noUnusedVariables": "error" "noUnusedVariables": "error"

View File

@@ -1,10 +1,5 @@
import ChartTimeSelect from "@/components/charts/chart-time-select" import { plural } from "@lingui/core/macro"
import { Button } from "@/components/ui/button" import { useLingui } from "@lingui/react/macro"
import { Card } from "@/components/ui/card"
import { FreeBsdIcon, TuxIcon, WebSocketIcon, WindowsIcon } from "@/components/ui/icons"
import { SystemStatus, ConnectionType, connectionTypeLabels, Os } from "@/lib/enums"
import { cn, formatBytes, getHostDisplayValue, secondsToString, toFixedFloat } from "@/lib/utils"
import { Separator } from "@/components/ui/separator"
import { import {
AppleIcon, AppleIcon,
ChevronRightSquareIcon, ChevronRightSquareIcon,
@@ -12,16 +7,21 @@ import {
CpuIcon, CpuIcon,
GlobeIcon, GlobeIcon,
LayoutGridIcon, LayoutGridIcon,
MemoryStickIcon,
MonitorIcon, MonitorIcon,
Rows, Rows,
MemoryStickIcon,
} from "lucide-react" } from "lucide-react"
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
import type { ChartData, SystemDetailsRecord, SystemRecord } from "@/types"
import { useEffect, useMemo, useState } from "react" import { useEffect, useMemo, useState } from "react"
import { useLingui } from "@lingui/react/macro" import ChartTimeSelect from "@/components/charts/chart-time-select"
import { Button } from "@/components/ui/button"
import { Card } from "@/components/ui/card"
import { FreeBsdIcon, TuxIcon, WebSocketIcon, WindowsIcon } from "@/components/ui/icons"
import { Separator } from "@/components/ui/separator"
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
import { pb } from "@/lib/api" import { pb } from "@/lib/api"
import { plural } from "@lingui/core/macro" import { ConnectionType, connectionTypeLabels, Os, SystemStatus } from "@/lib/enums"
import { cn, formatBytes, getHostDisplayValue, secondsToString, toFixedFloat } from "@/lib/utils"
import type { ChartData, SystemDetailsRecord, SystemRecord } from "@/types"
export default function InfoBar({ export default function InfoBar({
system, system,
@@ -41,6 +41,7 @@ export default function InfoBar({
// Fetch system_details on mount / when system changes // Fetch system_details on mount / when system changes
useEffect(() => { useEffect(() => {
let active = true
setDetails(null) setDetails(null)
// skip fetching system details if agent is older version which includes details in Info struct // skip fetching system details if agent is older version which includes details in Info struct
if (!system.id || system.info?.m) { if (!system.id || system.info?.m) {
@@ -54,10 +55,16 @@ export default function InfoBar({
}, },
}) })
.then((details) => { .then((details) => {
if (active) {
setDetails(details) setDetails(details)
setIsPodman(details.podman) setIsPodman(details.podman)
}
}) })
.catch(() => setDetails(null)) .catch(() => {})
return () => {
active = false
}
}, [system.id]) }, [system.id])
// values for system info bar - use details with fallback to system.info // values for system info bar - use details with fallback to system.info