change agent file structure

This commit is contained in:
TOomaAh
2024-08-09 17:52:02 +02:00
parent d840178cc0
commit 034a5c21eb
8 changed files with 281 additions and 297 deletions

View File

@@ -1,177 +0,0 @@
package main
import "time"
type SystemData struct {
Stats *SystemStats `json:"stats"`
Info *SystemInfo `json:"info"`
Containers []*ContainerStats `json:"container"`
}
type SystemInfo struct {
Cores int `json:"c"`
Threads int `json:"t"`
CpuModel string `json:"m"`
// Os string `json:"o"`
Uptime uint64 `json:"u"`
Cpu float64 `json:"cpu"`
MemPct float64 `json:"mp"`
DiskPct float64 `json:"dp"`
}
type SystemStats struct {
Cpu float64 `json:"cpu"`
Mem float64 `json:"m"`
MemUsed float64 `json:"mu"`
MemPct float64 `json:"mp"`
MemBuffCache float64 `json:"mb"`
Swap float64 `json:"s"`
SwapUsed float64 `json:"su"`
Disk float64 `json:"d"`
DiskUsed float64 `json:"du"`
DiskPct float64 `json:"dp"`
DiskRead float64 `json:"dr"`
DiskWrite float64 `json:"dw"`
NetworkSent float64 `json:"ns"`
NetworkRecv float64 `json:"nr"`
}
type ContainerStats struct {
Name string `json:"n"`
Cpu float64 `json:"c"`
Mem float64 `json:"m"`
NetworkSent float64 `json:"ns"`
NetworkRecv float64 `json:"nr"`
}
type Container struct {
Id string
IdShort string
Names []string
Status string
// Image string
// ImageID string
// Command string
// Created int64
// Ports []Port
// SizeRw int64 `json:",omitempty"`
// SizeRootFs int64 `json:",omitempty"`
// Labels map[string]string
// State string
// HostConfig struct {
// NetworkMode string `json:",omitempty"`
// Annotations map[string]string `json:",omitempty"`
// }
// NetworkSettings *SummaryNetworkSettings
// Mounts []MountPoint
}
type CStats struct {
// Common stats
// Read time.Time `json:"read"`
// PreRead time.Time `json:"preread"`
// Linux specific stats, not populated on Windows.
// PidsStats PidsStats `json:"pids_stats,omitempty"`
// BlkioStats BlkioStats `json:"blkio_stats,omitempty"`
// Windows specific stats, not populated on Linux.
// NumProcs uint32 `json:"num_procs"`
// StorageStats StorageStats `json:"storage_stats,omitempty"`
// Networks request version >=1.21
Networks map[string]NetworkStats
// Shared stats
CPUStats CPUStats `json:"cpu_stats,omitempty"`
// PreCPUStats CPUStats `json:"precpu_stats,omitempty"` // "Pre"="Previous"
MemoryStats MemoryStats `json:"memory_stats,omitempty"`
}
type CPUStats struct {
// CPU Usage. Linux and Windows.
CPUUsage CPUUsage `json:"cpu_usage"`
// System Usage. Linux only.
SystemUsage uint64 `json:"system_cpu_usage,omitempty"`
// Online CPUs. Linux only.
// OnlineCPUs uint32 `json:"online_cpus,omitempty"`
// Throttling Data. Linux only.
// ThrottlingData ThrottlingData `json:"throttling_data,omitempty"`
}
type CPUUsage struct {
// Total CPU time consumed.
// Units: nanoseconds (Linux)
// Units: 100's of nanoseconds (Windows)
TotalUsage uint64 `json:"total_usage"`
// Total CPU time consumed per core (Linux). Not used on Windows.
// Units: nanoseconds.
// PercpuUsage []uint64 `json:"percpu_usage,omitempty"`
// Time spent by tasks of the cgroup in kernel mode (Linux).
// Time spent by all container processes in kernel mode (Windows).
// Units: nanoseconds (Linux).
// Units: 100's of nanoseconds (Windows). Not populated for Hyper-V Containers.
// UsageInKernelmode uint64 `json:"usage_in_kernelmode"`
// Time spent by tasks of the cgroup in user mode (Linux).
// Time spent by all container processes in user mode (Windows).
// Units: nanoseconds (Linux).
// Units: 100's of nanoseconds (Windows). Not populated for Hyper-V Containers
// UsageInUsermode uint64 `json:"usage_in_usermode"`
}
type MemoryStats struct {
// current res_counter usage for memory
Usage uint64 `json:"usage,omitempty"`
Cache uint64 `json:"cache,omitempty"`
// maximum usage ever recorded.
// MaxUsage uint64 `json:"max_usage,omitempty"`
// TODO(vishh): Export these as stronger types.
// all the stats exported via memory.stat.
Stats map[string]uint64 `json:"stats,omitempty"`
// number of times memory usage hits limits.
// Failcnt uint64 `json:"failcnt,omitempty"`
// Limit uint64 `json:"limit,omitempty"`
// // committed bytes
// Commit uint64 `json:"commitbytes,omitempty"`
// // peak committed bytes
// CommitPeak uint64 `json:"commitpeakbytes,omitempty"`
// // private working set
// PrivateWorkingSet uint64 `json:"privateworkingset,omitempty"`
}
type NetworkStats struct {
// Bytes received. Windows and Linux.
RxBytes uint64 `json:"rx_bytes"`
// Bytes sent. Windows and Linux.
TxBytes uint64 `json:"tx_bytes"`
}
type DiskIoStats struct {
Read uint64
Write uint64
Time time.Time
Filesystem string
}
type NetIoStats struct {
BytesRecv uint64
BytesSent uint64
Time time.Time
Name string
}
type PrevContainerStats struct {
Cpu [2]uint64
Net struct {
Sent uint64
Recv uint64
Time time.Time
}
}

View File

@@ -1,54 +0,0 @@
package main
import (
"fmt"
"os"
"strings"
"github.com/blang/semver"
"github.com/rhysd/go-github-selfupdate/selfupdate"
)
func updateBeszel() {
var latest *selfupdate.Release
var found bool
var err error
currentVersion := semver.MustParse(Version)
fmt.Println("beszel-agent", currentVersion)
fmt.Println("Checking for updates...")
updater, _ := selfupdate.NewUpdater(selfupdate.Config{
Filters: []string{"beszel-agent"},
})
latest, found, err = updater.DetectLatest("henrygd/beszel")
if err != nil {
fmt.Println("Error checking for updates:", err)
os.Exit(1)
}
if !found {
fmt.Println("No updates found")
os.Exit(0)
}
fmt.Println("Latest version:", latest.Version)
if latest.Version.LTE(currentVersion) {
fmt.Println("You are up to date")
return
}
var binaryPath string
fmt.Printf("Updating from %s to %s...\n", currentVersion, latest.Version)
binaryPath, err = os.Executable()
if err != nil {
fmt.Println("Error getting binary path:", err)
os.Exit(1)
}
err = selfupdate.UpdateTo(latest.AssetURL, binaryPath)
if err != nil {
fmt.Println("Please try rerunning with sudo. Error:", err)
os.Exit(1)
}
fmt.Printf("Successfully updated to %s\n\n%s\n", latest.Version, strings.TrimSpace(latest.ReleaseNotes))
}

View File

@@ -1 +1,48 @@
package main
import (
"beszel"
"beszel/internal/agent"
"beszel/internal/update"
"fmt"
"log"
"os"
"strings"
)
func main() {
// handle flags / subcommands
if len(os.Args) > 1 {
switch os.Args[1] {
case "-v":
fmt.Println(beszel.AppName+"-agent", beszel.Version)
case "update":
update.UpdateBeszelAgent()
}
os.Exit(0)
}
var pubKey []byte
if pubKeyEnv, exists := os.LookupEnv("KEY"); exists {
pubKey = []byte(pubKeyEnv)
} else {
log.Fatal("KEY environment variable is not set")
}
var port string
if p, exists := os.LookupEnv("PORT"); exists {
// allow passing an address in the form of "127.0.0.1:45876"
if !strings.Contains(port, ":") {
port = ":" + port
}
port = p
} else {
port = ":45876"
}
a := agent.NewAgent(pubKey, port)
a.Run()
}

View File

@@ -1,6 +1,8 @@
package main
package agent
import (
"beszel/internal/entities/container"
"beszel/internal/entities/system"
"context"
"encoding/json"
"fmt"
@@ -15,38 +17,49 @@ import (
"sync"
"time"
sshServer "github.com/gliderlabs/ssh"
"github.com/shirou/gopsutil/v4/cpu"
"github.com/shirou/gopsutil/v4/disk"
"github.com/shirou/gopsutil/v4/host"
"github.com/shirou/gopsutil/v4/mem"
sshServer "github.com/gliderlabs/ssh"
psutilNet "github.com/shirou/gopsutil/v4/net"
)
var Version = "0.1.2"
var containerStatsMap = make(map[string]*PrevContainerStats)
var containerStatsMap = make(map[string]*container.PrevContainerStats)
var containerStatsMutex = &sync.Mutex{}
var sem = make(chan struct{}, 15)
func acquireSemaphore() {
sem <- struct{}{}
type Agent struct {
port string
pubKey []byte
sem chan struct{}
}
func releaseSemaphore() {
<-sem
func NewAgent(pubKey []byte, port string) *Agent {
return &Agent{
pubKey: pubKey,
sem: make(chan struct{}, 15),
}
}
var diskIoStats = DiskIoStats{
func (a *Agent) acquireSemaphore() {
a.sem <- struct{}{}
}
func (a *Agent) releaseSemaphore() {
<-a.sem
}
var diskIoStats = system.DiskIoStats{
Read: 0,
Write: 0,
Time: time.Now(),
Filesystem: "",
}
var netIoStats = NetIoStats{
var netIoStats = system.NetIoStats{
BytesRecv: 0,
BytesSent: 0,
Time: time.Now(),
@@ -56,8 +69,8 @@ var netIoStats = NetIoStats{
// client for docker engine api
var dockerClient = newDockerClient()
func getSystemStats() (*SystemInfo, *SystemStats) {
systemStats := &SystemStats{}
func getSystemStats() (*system.SystemInfo, *system.SystemStats) {
systemStats := &system.SystemStats{}
// cpu percent
cpuPct, err := cpu.Percent(0, false)
@@ -124,7 +137,7 @@ func getSystemStats() (*SystemInfo, *SystemStats) {
netIoStats.Time = time.Now()
}
systemInfo := &SystemInfo{
systemInfo := &system.SystemInfo{
Cpu: systemStats.Cpu,
MemPct: systemStats.MemPct,
DiskPct: systemStats.DiskPct,
@@ -150,21 +163,21 @@ func getSystemStats() (*SystemInfo, *SystemStats) {
}
func getDockerStats() ([]*ContainerStats, error) {
func (a *Agent) getDockerStats() ([]*container.ContainerStats, error) {
resp, err := dockerClient.Get("http://localhost/containers/json")
if err != nil {
closeIdleConnections(err)
return []*ContainerStats{}, err
return []*container.ContainerStats{}, err
}
defer resp.Body.Close()
var containers []*Container
var containers []*container.Container
if err := json.NewDecoder(resp.Body).Decode(&containers); err != nil {
log.Printf("Error decoding containers: %+v\n", err)
return []*ContainerStats{}, err
return []*container.ContainerStats{}, err
}
containerStats := make([]*ContainerStats, 0, len(containers))
containerStats := make([]*container.ContainerStats, 0, len(containers))
// store valid ids to clean up old container ids from map
validIds := make(map[string]struct{}, len(containers))
@@ -183,7 +196,7 @@ func getDockerStats() ([]*ContainerStats, error) {
wg.Add(1)
go func() {
defer wg.Done()
cstats, err := getContainerStats(ctr)
cstats, err := a.getContainerStats(ctr)
if err != nil {
// Check if the error is a network timeout
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
@@ -194,7 +207,7 @@ func getDockerStats() ([]*ContainerStats, error) {
deleteContainerStatsSync(ctr.IdShort)
}
// retry once
cstats, err = getContainerStats(ctr)
cstats, err = a.getContainerStats(ctr)
if err != nil {
log.Printf("Error getting container stats: %+v\n", err)
return
@@ -216,17 +229,17 @@ func getDockerStats() ([]*ContainerStats, error) {
return containerStats, nil
}
func getContainerStats(ctr *Container) (*ContainerStats, error) {
func (a *Agent) getContainerStats(ctr *container.Container) (*container.ContainerStats, error) {
// use semaphore to limit concurrency
acquireSemaphore()
defer releaseSemaphore()
a.acquireSemaphore()
defer a.releaseSemaphore()
resp, err := dockerClient.Get("http://localhost/containers/" + ctr.IdShort + "/stats?stream=0&one-shot=1")
if err != nil {
return &ContainerStats{}, err
return &container.ContainerStats{}, err
}
defer resp.Body.Close()
var statsJson CStats
var statsJson system.CStats
if err := json.NewDecoder(resp.Body).Decode(&statsJson); err != nil {
panic(err)
}
@@ -246,7 +259,7 @@ func getContainerStats(ctr *Container) (*ContainerStats, error) {
// add empty values if they doesn't exist in map
stats, initialized := containerStatsMap[ctr.IdShort]
if !initialized {
stats = &PrevContainerStats{}
stats = &container.PrevContainerStats{}
containerStatsMap[ctr.IdShort] = stats
}
@@ -255,7 +268,7 @@ func getContainerStats(ctr *Container) (*ContainerStats, error) {
systemDelta := statsJson.CPUStats.SystemUsage - stats.Cpu[1]
cpuPct := float64(cpuDelta) / float64(systemDelta) * 100
if cpuPct > 100 {
return &ContainerStats{}, fmt.Errorf("%s cpu pct greater than 100: %+v", name, cpuPct)
return &container.ContainerStats{}, fmt.Errorf("%s cpu pct greater than 100: %+v", name, cpuPct)
}
stats.Cpu = [2]uint64{statsJson.CPUStats.CPUUsage.TotalUsage, statsJson.CPUStats.SystemUsage}
@@ -277,7 +290,7 @@ func getContainerStats(ctr *Container) (*ContainerStats, error) {
stats.Net.Recv = total_recv
stats.Net.Time = time.Now()
cStats := &ContainerStats{
cStats := &container.ContainerStats{
Name: name,
Cpu: twoDecimals(cpuPct),
Mem: bytesToMegabytes(float64(usedMemory)),
@@ -294,14 +307,14 @@ func deleteContainerStatsSync(id string) {
delete(containerStatsMap, id)
}
func gatherStats() *SystemData {
func (a *Agent) gatherStats() *system.SystemData {
systemInfo, systemStats := getSystemStats()
stats := &SystemData{
stats := &system.SystemData{
Stats: systemStats,
Info: systemInfo,
Containers: []*ContainerStats{},
Containers: []*container.ContainerStats{},
}
containerStats, err := getDockerStats()
containerStats, err := a.getDockerStats()
if err == nil {
stats.Containers = containerStats
}
@@ -309,9 +322,9 @@ func gatherStats() *SystemData {
return stats
}
func startServer(addr string, pubKey []byte) {
func (a *Agent) startServer(addr string, pubKey []byte) {
sshServer.Handle(func(s sshServer.Session) {
stats := gatherStats()
stats := a.gatherStats()
var jsonStats []byte
jsonStats, _ = json.Marshal(stats)
io.WriteString(s, string(jsonStats))
@@ -330,24 +343,7 @@ func startServer(addr string, pubKey []byte) {
}
}
func main() {
// handle flags / subcommands
if len(os.Args) > 1 {
switch os.Args[1] {
case "-v":
fmt.Println("beszel-agent", Version)
case "update":
updateBeszel()
}
os.Exit(0)
}
var pubKey []byte
if pubKeyEnv, exists := os.LookupEnv("KEY"); exists {
pubKey = []byte(pubKeyEnv)
} else {
log.Fatal("KEY environment variable is not set")
}
func (a *Agent) Run() {
if filesystem, exists := os.LookupEnv("FILESYSTEM"); exists {
diskIoStats.Filesystem = filesystem
@@ -358,15 +354,7 @@ func main() {
initializeDiskIoStats()
initializeNetIoStats()
if port, exists := os.LookupEnv("PORT"); exists {
// allow passing an address in the form of "127.0.0.1:45876"
if !strings.Contains(port, ":") {
port = ":" + port
}
startServer(port, pubKey)
} else {
startServer(":45876", pubKey)
}
a.startServer(a.port, a.pubKey)
}
func bytesToMegabytes(b float64) float64 {

View File

@@ -1,5 +1,29 @@
package container
import "time"
type Container struct {
Id string
IdShort string
Names []string
Status string
// Image string
// ImageID string
// Command string
// Created int64
// Ports []Port
// SizeRw int64 `json:",omitempty"`
// SizeRootFs int64 `json:",omitempty"`
// Labels map[string]string
// State string
// HostConfig struct {
// NetworkMode string `json:",omitempty"`
// Annotations map[string]string `json:",omitempty"`
// }
// NetworkSettings *SummaryNetworkSettings
// Mounts []MountPoint
}
type ContainerStats struct {
Name string `json:"n"`
Cpu float64 `json:"c"`
@@ -7,3 +31,12 @@ type ContainerStats struct {
NetworkSent float64 `json:"ns"`
NetworkRecv float64 `json:"nr"`
}
type PrevContainerStats struct {
Cpu [2]uint64
Net struct {
Sent uint64
Recv uint64
Time time.Time
}
}

View File

@@ -1,5 +1,7 @@
package system
import "time"
type SystemStats struct {
Cpu float64 `json:"cpu"`
Mem float64 `json:"m"`
@@ -16,3 +18,104 @@ type SystemStats struct {
NetworkSent float64 `json:"ns"`
NetworkRecv float64 `json:"nr"`
}
type DiskIoStats struct {
Read uint64
Write uint64
Time time.Time
Filesystem string
}
type NetIoStats struct {
BytesRecv uint64
BytesSent uint64
Time time.Time
Name string
}
type CPUStats struct {
// CPU Usage. Linux and Windows.
CPUUsage CPUUsage `json:"cpu_usage"`
// System Usage. Linux only.
SystemUsage uint64 `json:"system_cpu_usage,omitempty"`
// Online CPUs. Linux only.
// OnlineCPUs uint32 `json:"online_cpus,omitempty"`
// Throttling Data. Linux only.
// ThrottlingData ThrottlingData `json:"throttling_data,omitempty"`
}
type CPUUsage struct {
// Total CPU time consumed.
// Units: nanoseconds (Linux)
// Units: 100's of nanoseconds (Windows)
TotalUsage uint64 `json:"total_usage"`
// Total CPU time consumed per core (Linux). Not used on Windows.
// Units: nanoseconds.
// PercpuUsage []uint64 `json:"percpu_usage,omitempty"`
// Time spent by tasks of the cgroup in kernel mode (Linux).
// Time spent by all container processes in kernel mode (Windows).
// Units: nanoseconds (Linux).
// Units: 100's of nanoseconds (Windows). Not populated for Hyper-V Containers.
// UsageInKernelmode uint64 `json:"usage_in_kernelmode"`
// Time spent by tasks of the cgroup in user mode (Linux).
// Time spent by all container processes in user mode (Windows).
// Units: nanoseconds (Linux).
// Units: 100's of nanoseconds (Windows). Not populated for Hyper-V Containers
// UsageInUsermode uint64 `json:"usage_in_usermode"`
}
type CStats struct {
// Common stats
// Read time.Time `json:"read"`
// PreRead time.Time `json:"preread"`
// Linux specific stats, not populated on Windows.
// PidsStats PidsStats `json:"pids_stats,omitempty"`
// BlkioStats BlkioStats `json:"blkio_stats,omitempty"`
// Windows specific stats, not populated on Linux.
// NumProcs uint32 `json:"num_procs"`
// StorageStats StorageStats `json:"storage_stats,omitempty"`
// Networks request version >=1.21
Networks map[string]NetworkStats
// Shared stats
CPUStats CPUStats `json:"cpu_stats,omitempty"`
// PreCPUStats CPUStats `json:"precpu_stats,omitempty"` // "Pre"="Previous"
MemoryStats MemoryStats `json:"memory_stats,omitempty"`
}
type MemoryStats struct {
// current res_counter usage for memory
Usage uint64 `json:"usage,omitempty"`
Cache uint64 `json:"cache,omitempty"`
// maximum usage ever recorded.
// MaxUsage uint64 `json:"max_usage,omitempty"`
// TODO(vishh): Export these as stronger types.
// all the stats exported via memory.stat.
Stats map[string]uint64 `json:"stats,omitempty"`
// number of times memory usage hits limits.
// Failcnt uint64 `json:"failcnt,omitempty"`
// Limit uint64 `json:"limit,omitempty"`
// // committed bytes
// Commit uint64 `json:"commitbytes,omitempty"`
// // peak committed bytes
// CommitPeak uint64 `json:"commitpeakbytes,omitempty"`
// // private working set
// PrivateWorkingSet uint64 `json:"privateworkingset,omitempty"`
}
type NetworkStats struct {
// Bytes received. Windows and Linux.
RxBytes uint64 `json:"rx_bytes"`
// Bytes sent. Windows and Linux.
TxBytes uint64 `json:"tx_bytes"`
}

View File

@@ -14,7 +14,7 @@ type SystemInfo struct {
}
type SystemData struct {
Stats SystemStats `json:"stats"`
Info SystemInfo `json:"info"`
Containers []container.ContainerStats `json:"container"`
Stats *SystemStats `json:"stats"`
Info *SystemInfo `json:"info"`
Containers []*container.ContainerStats `json:"container"`
}

View File

@@ -54,3 +54,47 @@ func UpdateBeszel(cmd *cobra.Command, args []string) {
}
fmt.Printf("Successfully updated to %s\n\n%s\n", latest.Version, strings.TrimSpace(latest.ReleaseNotes))
}
func UpdateBeszelAgent() {
var latest *selfupdate.Release
var found bool
var err error
currentVersion := semver.MustParse(beszel.Version)
fmt.Println("beszel-agent", currentVersion)
fmt.Println("Checking for updates...")
updater, _ := selfupdate.NewUpdater(selfupdate.Config{
Filters: []string{"beszel-agent"},
})
latest, found, err = updater.DetectLatest("henrygd/beszel")
if err != nil {
fmt.Println("Error checking for updates:", err)
os.Exit(1)
}
if !found {
fmt.Println("No updates found")
os.Exit(0)
}
fmt.Println("Latest version:", latest.Version)
if latest.Version.LTE(currentVersion) {
fmt.Println("You are up to date")
return
}
var binaryPath string
fmt.Printf("Updating from %s to %s...\n", currentVersion, latest.Version)
binaryPath, err = os.Executable()
if err != nil {
fmt.Println("Error getting binary path:", err)
os.Exit(1)
}
err = selfupdate.UpdateTo(latest.AssetURL, binaryPath)
if err != nil {
fmt.Println("Please try rerunning with sudo. Error:", err)
os.Exit(1)
}
fmt.Printf("Successfully updated to %s\n\n%s\n", latest.Version, strings.TrimSpace(latest.ReleaseNotes))
}