mirror of
https://github.com/fankes/beszel.git
synced 2025-10-19 01:39:34 +08:00
[Feature][0.12.0-Beta] Enhance Load Average Display, Charting & Alert Grouping (#960)
* Add 1m load * update alart dialog * fix null data * Remove omit zero * change table and alert view
This commit is contained in:
@@ -56,7 +56,7 @@ dev-hub: export ENV=dev
|
|||||||
dev-hub:
|
dev-hub:
|
||||||
mkdir -p ./site/dist && touch ./site/dist/index.html
|
mkdir -p ./site/dist && touch ./site/dist/index.html
|
||||||
@if command -v entr >/dev/null 2>&1; then \
|
@if command -v entr >/dev/null 2>&1; then \
|
||||||
find ./cmd/hub ./internal/{alerts,hub,records,users} -name "*.go" | entr -r -s "cd ./cmd/hub && go run . serve"; \
|
find ./cmd/hub/*.go ./internal/{alerts,hub,records,users}/*.go | entr -r -s "cd ./cmd/hub && go run . serve --http 0.0.0.0:8090"; \
|
||||||
else \
|
else \
|
||||||
cd ./cmd/hub && go run . serve --http 0.0.0.0:8090; \
|
cd ./cmd/hub && go run . serve --http 0.0.0.0:8090; \
|
||||||
fi
|
fi
|
||||||
|
@@ -251,6 +251,7 @@ func (a *Agent) getSystemStats() system.Stats {
|
|||||||
|
|
||||||
// update base system info
|
// update base system info
|
||||||
a.systemInfo.Cpu = systemStats.Cpu
|
a.systemInfo.Cpu = systemStats.Cpu
|
||||||
|
a.systemInfo.LoadAvg1 = systemStats.LoadAvg1
|
||||||
a.systemInfo.LoadAvg5 = systemStats.LoadAvg5
|
a.systemInfo.LoadAvg5 = systemStats.LoadAvg5
|
||||||
a.systemInfo.LoadAvg15 = systemStats.LoadAvg15
|
a.systemInfo.LoadAvg15 = systemStats.LoadAvg15
|
||||||
a.systemInfo.MemPct = systemStats.MemPct
|
a.systemInfo.MemPct = systemStats.MemPct
|
||||||
|
@@ -47,6 +47,7 @@ type SystemAlertStats struct {
|
|||||||
NetSent float64 `json:"ns"`
|
NetSent float64 `json:"ns"`
|
||||||
NetRecv float64 `json:"nr"`
|
NetRecv float64 `json:"nr"`
|
||||||
Temperatures map[string]float32 `json:"t"`
|
Temperatures map[string]float32 `json:"t"`
|
||||||
|
LoadAvg1 float64 `json:"l1"`
|
||||||
LoadAvg5 float64 `json:"l5"`
|
LoadAvg5 float64 `json:"l5"`
|
||||||
LoadAvg15 float64 `json:"l15"`
|
LoadAvg15 float64 `json:"l15"`
|
||||||
}
|
}
|
||||||
|
@@ -54,6 +54,9 @@ func (am *AlertManager) HandleSystemAlerts(systemRecord *core.Record, data *syst
|
|||||||
}
|
}
|
||||||
val = data.Info.DashboardTemp
|
val = data.Info.DashboardTemp
|
||||||
unit = "°C"
|
unit = "°C"
|
||||||
|
case "LoadAvg1":
|
||||||
|
val = data.Info.LoadAvg1
|
||||||
|
unit = ""
|
||||||
case "LoadAvg5":
|
case "LoadAvg5":
|
||||||
val = data.Info.LoadAvg5
|
val = data.Info.LoadAvg5
|
||||||
unit = ""
|
unit = ""
|
||||||
@@ -196,6 +199,8 @@ func (am *AlertManager) HandleSystemAlerts(systemRecord *core.Record, data *syst
|
|||||||
}
|
}
|
||||||
alert.mapSums[key] += temp
|
alert.mapSums[key] += temp
|
||||||
}
|
}
|
||||||
|
case "LoadAvg1":
|
||||||
|
alert.val += stats.LoadAvg1
|
||||||
case "LoadAvg5":
|
case "LoadAvg5":
|
||||||
alert.val += stats.LoadAvg5
|
alert.val += stats.LoadAvg5
|
||||||
case "LoadAvg15":
|
case "LoadAvg15":
|
||||||
|
@@ -92,8 +92,9 @@ type Info struct {
|
|||||||
GpuPct float64 `json:"g,omitempty" cbor:"12,keyasint,omitempty"`
|
GpuPct float64 `json:"g,omitempty" cbor:"12,keyasint,omitempty"`
|
||||||
DashboardTemp float64 `json:"dt,omitempty" cbor:"13,keyasint,omitempty"`
|
DashboardTemp float64 `json:"dt,omitempty" cbor:"13,keyasint,omitempty"`
|
||||||
Os Os `json:"os" cbor:"14,keyasint"`
|
Os Os `json:"os" cbor:"14,keyasint"`
|
||||||
LoadAvg5 float64 `json:"l5,omitempty" cbor:"15,keyasint,omitempty,omitzero"`
|
LoadAvg1 float64 `json:"l1,omitempty" cbor:"15,keyasint,omitempty"`
|
||||||
LoadAvg15 float64 `json:"l15,omitempty" cbor:"16,keyasint,omitempty,omitzero"`
|
LoadAvg5 float64 `json:"l5,omitempty" cbor:"16,keyasint,omitempty"`
|
||||||
|
LoadAvg15 float64 `json:"l15,omitempty" cbor:"17,keyasint,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Final data structure to return to the hub
|
// Final data structure to return to the hub
|
||||||
|
@@ -76,6 +76,7 @@ func init() {
|
|||||||
"Disk",
|
"Disk",
|
||||||
"Temperature",
|
"Temperature",
|
||||||
"Bandwidth",
|
"Bandwidth",
|
||||||
|
"LoadAvg1",
|
||||||
"LoadAvg5",
|
"LoadAvg5",
|
||||||
"LoadAvg15"
|
"LoadAvg15"
|
||||||
]
|
]
|
@@ -11,13 +11,14 @@ import {
|
|||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "@/components/ui/dialog"
|
} from "@/components/ui/dialog"
|
||||||
import { BellIcon, GlobeIcon, ServerIcon } from "lucide-react"
|
import { BellIcon, GlobeIcon, ServerIcon, HourglassIcon } from "lucide-react"
|
||||||
import { alertInfo, cn } from "@/lib/utils"
|
import { alertInfo, cn } from "@/lib/utils"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { AlertRecord, SystemRecord } from "@/types"
|
import { AlertRecord, SystemRecord } from "@/types"
|
||||||
import { $router, Link } from "../router"
|
import { $router, Link } from "../router"
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||||
import { Checkbox } from "../ui/checkbox"
|
import { Checkbox } from "../ui/checkbox"
|
||||||
|
import { Collapsible } from "../ui/collapsible"
|
||||||
import { SystemAlert, SystemAlertGlobal } from "./alerts-system"
|
import { SystemAlert, SystemAlertGlobal } from "./alerts-system"
|
||||||
import { getPagePath } from "@nanostores/router"
|
import { getPagePath } from "@nanostores/router"
|
||||||
|
|
||||||
|
123
beszel/site/src/components/charts/load-average-chart.tsx
Normal file
123
beszel/site/src/components/charts/load-average-chart.tsx
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
import { CartesianGrid, Line, LineChart, YAxis } from "recharts"
|
||||||
|
|
||||||
|
import {
|
||||||
|
ChartContainer,
|
||||||
|
ChartLegend,
|
||||||
|
ChartLegendContent,
|
||||||
|
ChartTooltip,
|
||||||
|
ChartTooltipContent,
|
||||||
|
xAxis,
|
||||||
|
} from "@/components/ui/chart"
|
||||||
|
import {
|
||||||
|
useYAxisWidth,
|
||||||
|
cn,
|
||||||
|
formatShortDate,
|
||||||
|
toFixedWithoutTrailingZeros,
|
||||||
|
decimalString,
|
||||||
|
chartMargin,
|
||||||
|
} from "@/lib/utils"
|
||||||
|
import { ChartData } from "@/types"
|
||||||
|
import { memo, useMemo } from "react"
|
||||||
|
import { t } from "@lingui/core/macro"
|
||||||
|
|
||||||
|
export default memo(function LoadAverageChart({ chartData }: { chartData: ChartData }) {
|
||||||
|
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
|
||||||
|
|
||||||
|
if (chartData.systemStats.length === 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Format load average data for chart */
|
||||||
|
const newChartData = useMemo(() => {
|
||||||
|
const newChartData = { data: [], colors: {} } as {
|
||||||
|
data: Record<string, number | string>[]
|
||||||
|
colors: Record<string, string>
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define colors for the three load average lines
|
||||||
|
const colors = {
|
||||||
|
"1m": "hsl(25, 95%, 53%)", // Orange for 1-minute
|
||||||
|
"5m": "hsl(217, 91%, 60%)", // Blue for 5-minute
|
||||||
|
"15m": "hsl(271, 81%, 56%)", // Purple for 15-minute
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let data of chartData.systemStats) {
|
||||||
|
let newData = { created: data.created } as Record<string, number | string>
|
||||||
|
|
||||||
|
// Add load average values if they exist and stats is not null
|
||||||
|
if (data.stats && data.stats.l1 !== undefined) {
|
||||||
|
newData["1m"] = data.stats.l1
|
||||||
|
}
|
||||||
|
if (data.stats && data.stats.l5 !== undefined) {
|
||||||
|
newData["5m"] = data.stats.l5
|
||||||
|
}
|
||||||
|
if (data.stats && data.stats.l15 !== undefined) {
|
||||||
|
newData["15m"] = data.stats.l15
|
||||||
|
}
|
||||||
|
|
||||||
|
newChartData.data.push(newData)
|
||||||
|
}
|
||||||
|
|
||||||
|
newChartData.colors = colors
|
||||||
|
return newChartData
|
||||||
|
}, [chartData])
|
||||||
|
|
||||||
|
const loadKeys = ["1m", "5m", "15m"].filter(key =>
|
||||||
|
newChartData.data.some(data => data[key] !== undefined)
|
||||||
|
)
|
||||||
|
|
||||||
|
// console.log('rendered at', new Date())
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<ChartContainer
|
||||||
|
className={cn("h-full w-full absolute aspect-auto bg-card opacity-0 transition-opacity", {
|
||||||
|
"opacity-100": yAxisWidth,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<LineChart accessibilityLayer data={newChartData.data} margin={chartMargin}>
|
||||||
|
<CartesianGrid vertical={false} />
|
||||||
|
<YAxis
|
||||||
|
direction="ltr"
|
||||||
|
orientation={chartData.orientation}
|
||||||
|
className="tracking-tighter"
|
||||||
|
domain={[0, "auto"]}
|
||||||
|
width={yAxisWidth}
|
||||||
|
tickFormatter={(value) => {
|
||||||
|
const val = toFixedWithoutTrailingZeros(value, 2)
|
||||||
|
return updateYAxisWidth(val)
|
||||||
|
}}
|
||||||
|
tickLine={false}
|
||||||
|
axisLine={false}
|
||||||
|
/>
|
||||||
|
{xAxis(chartData)}
|
||||||
|
<ChartTooltip
|
||||||
|
animationEasing="ease-out"
|
||||||
|
animationDuration={150}
|
||||||
|
// @ts-ignore
|
||||||
|
itemSorter={(a, b) => b.value - a.value}
|
||||||
|
content={
|
||||||
|
<ChartTooltipContent
|
||||||
|
labelFormatter={(_, data) => formatShortDate(data[0].payload.created)}
|
||||||
|
contentFormatter={(item) => decimalString(item.value)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{loadKeys.map((key) => (
|
||||||
|
<Line
|
||||||
|
key={key}
|
||||||
|
dataKey={key}
|
||||||
|
name={key === "1m" ? t`1 min` : key === "5m" ? t`5 min` : t`15 min`}
|
||||||
|
type="monotoneX"
|
||||||
|
dot={false}
|
||||||
|
strokeWidth={1.5}
|
||||||
|
stroke={newChartData.colors[key]}
|
||||||
|
isAnimationActive={false}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<ChartLegend content={<ChartLegendContent />} />
|
||||||
|
</LineChart>
|
||||||
|
</ChartContainer>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
@@ -48,6 +48,7 @@ const DiskChart = lazy(() => import("../charts/disk-chart"))
|
|||||||
const SwapChart = lazy(() => import("../charts/swap-chart"))
|
const SwapChart = lazy(() => import("../charts/swap-chart"))
|
||||||
const TemperatureChart = lazy(() => import("../charts/temperature-chart"))
|
const TemperatureChart = lazy(() => import("../charts/temperature-chart"))
|
||||||
const GpuPowerChart = lazy(() => import("../charts/gpu-power-chart"))
|
const GpuPowerChart = lazy(() => import("../charts/gpu-power-chart"))
|
||||||
|
const LoadAverageChart = lazy(() => import("../charts/load-average-chart"))
|
||||||
|
|
||||||
const cache = new Map<string, any>()
|
const cache = new Map<string, any>()
|
||||||
|
|
||||||
@@ -483,6 +484,18 @@ export default function SystemDetail({ name }: { name: string }) {
|
|||||||
/>
|
/>
|
||||||
</ChartCard>
|
</ChartCard>
|
||||||
|
|
||||||
|
{/* Load Average chart */}
|
||||||
|
{(systemStats.at(-1)?.stats.l1 !== undefined || systemStats.at(-1)?.stats.l5 !== undefined || systemStats.at(-1)?.stats.l15 !== undefined) && (
|
||||||
|
<ChartCard
|
||||||
|
empty={dataEmpty}
|
||||||
|
grid={grid}
|
||||||
|
title={t`Load Average`}
|
||||||
|
description={t`System load averages over time`}
|
||||||
|
>
|
||||||
|
<LoadAverageChart chartData={chartData} />
|
||||||
|
</ChartCard>
|
||||||
|
)}
|
||||||
|
|
||||||
{containerFilterBar && (
|
{containerFilterBar && (
|
||||||
<ChartCard
|
<ChartCard
|
||||||
empty={dataEmpty}
|
empty={dataEmpty}
|
||||||
|
@@ -84,6 +84,7 @@ import { ClassValue } from "clsx"
|
|||||||
import { getPagePath } from "@nanostores/router"
|
import { getPagePath } from "@nanostores/router"
|
||||||
import { SystemDialog } from "../add-system"
|
import { SystemDialog } from "../add-system"
|
||||||
import { Dialog } from "../ui/dialog"
|
import { Dialog } from "../ui/dialog"
|
||||||
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../ui/tooltip"
|
||||||
|
|
||||||
type ViewMode = "table" | "grid"
|
type ViewMode = "table" | "grid"
|
||||||
|
|
||||||
@@ -239,43 +240,61 @@ export default function SystemsTable() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorFn: (originalRow) => originalRow.info.l5,
|
id: "loadAverage",
|
||||||
id: "l5",
|
name: () => t`Load Average`,
|
||||||
name: () => t({ message: "L5", comment: "Load average 5 minutes" }),
|
|
||||||
size: 0,
|
size: 0,
|
||||||
hideSort: true,
|
hideSort: true,
|
||||||
Icon: HourglassIcon,
|
Icon: HourglassIcon,
|
||||||
header: sortableHeader,
|
header: sortableHeader,
|
||||||
cell(info) {
|
cell(info: CellContext<SystemRecord, unknown>) {
|
||||||
const val = info.getValue() as number
|
const system = info.row.original;
|
||||||
if (!val) {
|
const l1 = system.info?.l1;
|
||||||
return null
|
const l5 = system.info?.l5;
|
||||||
|
const l15 = system.info?.l15;
|
||||||
|
const cores = system.info?.c || 1;
|
||||||
|
|
||||||
|
// If no load average data, return null
|
||||||
|
if (!l1 && !l5 && !l15) return null;
|
||||||
|
|
||||||
|
const loadAverages = [
|
||||||
|
{ name: "1m", value: l1 },
|
||||||
|
{ name: "5m", value: l5 },
|
||||||
|
{ name: "15m", value: l15 }
|
||||||
|
].filter(la => la.value !== undefined);
|
||||||
|
|
||||||
|
if (!loadAverages.length) return null;
|
||||||
|
|
||||||
|
function getDotColor(value: number) {
|
||||||
|
const normalized = value / cores;
|
||||||
|
if (normalized < 0.7) return "bg-green-500";
|
||||||
|
if (normalized < 1.0) return "bg-orange-500";
|
||||||
|
return "bg-red-600";
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className={cn("tabular-nums whitespace-nowrap", viewMode === "table" && "ps-1")}>
|
<div className="flex items-center gap-2 w-full">
|
||||||
{decimalString(val)}
|
{loadAverages.map((la, idx) => (
|
||||||
</span>
|
<TooltipProvider key={la.name}>
|
||||||
)
|
<Tooltip>
|
||||||
},
|
<TooltipTrigger asChild>
|
||||||
},
|
<span className="flex items-center cursor-pointer">
|
||||||
{
|
<span className={cn("inline-block w-2 h-2 rounded-full mr-1", getDotColor(la.value || 0))} />
|
||||||
accessorFn: (originalRow) => originalRow.info.l15,
|
<span className="tabular-nums">
|
||||||
id: "l15",
|
{decimalString(la.value || 0, 2)}
|
||||||
name: () => t({ message: "L15", comment: "Load average 15 minutes" }),
|
</span>
|
||||||
size: 0,
|
{idx < loadAverages.length - 1 && <span className="mx-1 text-muted-foreground">/</span>}
|
||||||
hideSort: true,
|
</span>
|
||||||
Icon: HourglassIcon,
|
</TooltipTrigger>
|
||||||
header: sortableHeader,
|
<TooltipContent side="top">
|
||||||
cell(info) {
|
<div className="text-center">
|
||||||
const val = info.getValue() as number
|
<div className="font-medium">{t`${la.name}`}</div>
|
||||||
if (!val) {
|
</div>
|
||||||
return null
|
</TooltipContent>
|
||||||
}
|
</Tooltip>
|
||||||
return (
|
</TooltipProvider>
|
||||||
<span className={cn("tabular-nums whitespace-nowrap", viewMode === "table" && "ps-1")}>
|
))}
|
||||||
{decimalString(val)}
|
</div>
|
||||||
</span>
|
);
|
||||||
)
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
49
beszel/site/src/components/ui/collapsible.tsx
Normal file
49
beszel/site/src/components/ui/collapsible.tsx
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { ChevronDownIcon, HourglassIcon } from "lucide-react"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { Button } from "./button"
|
||||||
|
|
||||||
|
interface CollapsibleProps {
|
||||||
|
title: string
|
||||||
|
children: React.ReactNode
|
||||||
|
description?: React.ReactNode
|
||||||
|
defaultOpen?: boolean
|
||||||
|
className?: string
|
||||||
|
icon?: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Collapsible({ title, children, description, defaultOpen = false, className, icon }: CollapsibleProps) {
|
||||||
|
const [isOpen, setIsOpen] = React.useState(defaultOpen)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cn("border rounded-lg", className)}>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
className="w-full justify-between p-4 font-semibold"
|
||||||
|
onClick={() => setIsOpen(!isOpen)}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{icon}
|
||||||
|
{title}
|
||||||
|
</div>
|
||||||
|
<ChevronDownIcon
|
||||||
|
className={cn("h-4 w-4 transition-transform duration-200", {
|
||||||
|
"rotate-180": isOpen,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
{description && (
|
||||||
|
<div className="px-4 pb-2 text-sm text-muted-foreground">
|
||||||
|
{description}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{isOpen && (
|
||||||
|
<div className="px-4 pb-4">
|
||||||
|
<div className="grid gap-3">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@@ -407,6 +407,16 @@ export const alertInfo: Record<string, AlertInfo> = {
|
|||||||
icon: ThermometerIcon,
|
icon: ThermometerIcon,
|
||||||
desc: () => t`Triggers when any sensor exceeds a threshold`,
|
desc: () => t`Triggers when any sensor exceeds a threshold`,
|
||||||
},
|
},
|
||||||
|
LoadAvg1: {
|
||||||
|
name: () => t`Load Average 1m`,
|
||||||
|
unit: "",
|
||||||
|
icon: HourglassIcon,
|
||||||
|
max: 100,
|
||||||
|
min: 0.1,
|
||||||
|
start: 10,
|
||||||
|
step: 0.1,
|
||||||
|
desc: () => t`Triggers when 1 minute load average exceeds a threshold`,
|
||||||
|
},
|
||||||
LoadAvg5: {
|
LoadAvg5: {
|
||||||
name: () => t`Load Average 5m`,
|
name: () => t`Load Average 5m`,
|
||||||
unit: "",
|
unit: "",
|
||||||
@@ -445,3 +455,27 @@ export const getHubURL = () => BESZEL?.HUB_URL || window.location.origin
|
|||||||
|
|
||||||
/** Map of system IDs to their corresponding tokens (used to avoid fetching in add-system dialog) */
|
/** Map of system IDs to their corresponding tokens (used to avoid fetching in add-system dialog) */
|
||||||
export const tokenMap = new Map<SystemRecord["id"], FingerprintRecord["token"]>()
|
export const tokenMap = new Map<SystemRecord["id"], FingerprintRecord["token"]>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate load average percentage relative to CPU cores
|
||||||
|
* @param loadAverage - The load average value (1m, 5m, or 15m)
|
||||||
|
* @param cores - Number of CPU cores
|
||||||
|
* @returns Percentage (0-100) representing CPU utilization
|
||||||
|
*/
|
||||||
|
export const calculateLoadAveragePercent = (loadAverage: number, cores: number): number => {
|
||||||
|
if (!loadAverage || !cores) return 0
|
||||||
|
return Math.min((loadAverage / cores) * 100, 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get load average opacity based on utilization relative to cores
|
||||||
|
* @param loadAverage - The load average value
|
||||||
|
* @param cores - Number of CPU cores
|
||||||
|
* @returns Opacity value (0.6, 0.8, or 1.0)
|
||||||
|
*/
|
||||||
|
export const getLoadAverageOpacity = (loadAverage: number, cores: number): number => {
|
||||||
|
if (!loadAverage || !cores) return 0.6
|
||||||
|
if (loadAverage < cores * 0.5) return 0.6
|
||||||
|
if (loadAverage < cores) return 0.8
|
||||||
|
return 1.0
|
||||||
|
}
|
2
beszel/site/src/types.d.ts
vendored
2
beszel/site/src/types.d.ts
vendored
@@ -44,6 +44,8 @@ export interface SystemInfo {
|
|||||||
c: number
|
c: number
|
||||||
/** cpu model */
|
/** cpu model */
|
||||||
m: string
|
m: string
|
||||||
|
/** load average 1 minute */
|
||||||
|
l1?: number
|
||||||
/** load average 5 minutes */
|
/** load average 5 minutes */
|
||||||
l5?: number
|
l5?: number
|
||||||
/** load average 15 minutes */
|
/** load average 15 minutes */
|
||||||
|
Reference in New Issue
Block a user