Files
godoxy-yusing/internal/proxmox/lxc_stats.go
yusing 978dd886c0 refactor(proxmox): change VMID type from int to uint64 across Proxmox provider
Updates VMID parameter and field types from int to uint64 throughout the Proxmox provider implementation,
including API request structures, provider structs, client methods, and LXC-related functions.
Also updates string conversion calls from strconv.Itoa to strconv.FormatUint.
2026-02-10 16:53:07 +08:00

172 lines
5.0 KiB
Go

package proxmox
import (
"bytes"
"context"
"fmt"
"io"
"strings"
"time"
)
// const statsScriptLocation = "/tmp/godoxy-stats.sh"
// const statsScript = `#!/bin/sh
// # LXCStats script, written by godoxy.
// printf "%s|%s|%s|%s|%s\n" \
// "$(top -bn1 | grep "Cpu(s)" | sed "s/.*, *\([0-9.]*\)%* id.*/\1/" | awk '{print 100 - $1"%"}')" \
// "$(free -b | awk 'NR==2{printf "%.0f\n%.0f", $3, $2}' | numfmt --to=iec-i --suffix=B | paste -sd/)" \
// "$(free | awk 'NR==2{printf "%.2f%%", $3/$2*100}')" \
// "$(awk 'NR>2{r+=$2;t+=$10}END{printf "%.0f\n%.0f", r, t}' /proc/net/dev | numfmt --to=iec-i --suffix=B | paste -sd/)" \
// "$(awk '{r+=$6;w+=$10}END{printf "%.0f\n%.0f", r*512, w*512}' /proc/diskstats | numfmt --to=iec-i --suffix=B | paste -sd/)"`
// var statsScriptBase64 = base64.StdEncoding.EncodeToString([]byte(statsScript))
// var statsInitCommand = fmt.Sprintf("sh -c 'echo %s | base64 -d > %s && chmod +x %s'", statsScriptBase64, statsScriptLocation, statsScriptLocation)
// var statsStreamScript = fmt.Sprintf("watch -t -w -p -n1 '%s'", statsScriptLocation)
// var statsNonStreamScript = statsScriptLocation
// lxcStatsScriptInit initializes the stats script for the given container.
// func (n *Node) lxcStatsScriptInit(ctx context.Context, vmid int) error {
// reader, err := n.LXCCommand(ctx, vmid, statsInitCommand)
// if err != nil {
// return fmt.Errorf("failed to execute stats init command: %w", err)
// }
// reader.Close()
// return nil
// }
// LXCStats streams container stats, like docker stats.
//
// - format: "STATUS|CPU%%|MEM USAGE/LIMIT|MEM%%|NET I/O|BLOCK I/O"
// - example: running|31.1%|9.6GiB/20GiB|48.87%|4.7GiB/3.3GiB|25GiB/36GiB
func (n *Node) LXCStats(ctx context.Context, vmid uint64, stream bool) (io.ReadCloser, error) {
if !stream {
resource, err := n.client.GetResource("lxc", vmid)
if err != nil {
return nil, err
}
var buf bytes.Buffer
if err := writeLXCStatsLine(resource, &buf); err != nil {
return nil, err
}
return io.NopCloser(&buf), nil
}
// Validate the resource exists before returning a stream.
_, err := n.client.GetResource("lxc", vmid)
if err != nil {
return nil, err
}
pr, pw := io.Pipe()
interval := ResourcePollInterval
if interval <= 0 {
interval = time.Second
}
go func() {
writeSample := func() error {
resource, err := n.client.GetResource("lxc", vmid)
if err != nil {
return err
}
err = writeLXCStatsLine(resource, pw)
return err
}
// Match `watch` behavior: write immediately, then on each tick.
if err := writeSample(); err != nil {
_ = pw.CloseWithError(err)
return
}
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
_ = pw.CloseWithError(ctx.Err())
return
case <-ticker.C:
if err := writeSample(); err != nil {
_ = pw.CloseWithError(err)
return
}
}
}
}()
return pr, nil
}
func writeLXCStatsLine(resource *VMResource, w io.Writer) error {
cpu := fmt.Sprintf("%.1f%%", resource.CPU*100)
memUsage := formatIECBytes(resource.Mem)
memLimit := formatIECBytes(resource.MaxMem)
memPct := "0.00%"
if resource.MaxMem > 0 {
memPct = fmt.Sprintf("%.2f%%", float64(resource.Mem)/float64(resource.MaxMem)*100)
}
netIO := formatIECBytes(resource.NetIn) + "/" + formatIECBytes(resource.NetOut)
blockIO := formatIECBytes(resource.DiskRead) + "/" + formatIECBytes(resource.DiskWrite)
// Keep the format consistent with LXCStatsAlt / `statsScript` (newline terminated).
_, err := fmt.Fprintf(w, "%s|%s|%s/%s|%s|%s|%s\n", resource.Status, cpu, memUsage, memLimit, memPct, netIO, blockIO)
return err
}
// formatIECBytes formats a byte count using IEC binary prefixes (KiB, MiB, GiB, ...),
// similar to `numfmt --to=iec-i --suffix=B`.
func formatIECBytes(b uint64) string {
const unit = 1024
if b < unit {
return fmt.Sprintf("%dB", b)
}
prefixes := []string{"B", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei"}
val := float64(b)
exp := 0
for val >= unit && exp < len(prefixes)-1 {
val /= unit
exp++
}
// One decimal, trimming trailing ".0" to keep output compact (e.g. "10GiB").
s := fmt.Sprintf("%.1f", val)
s = strings.TrimSuffix(s, ".0")
if exp == 0 {
return s + "B"
}
return s + prefixes[exp] + "B"
}
// LXCStatsAlt streams container stats, like docker stats.
//
// - format: "CPU%%|MEM USAGE/LIMIT|MEM%%|NET I/O|BLOCK I/O"
// - example: 31.1%|9.6GiB/20GiB|48.87%|4.7GiB/3.3GiB|25TiB/36TiB
// func (n *Node) LXCStatsAlt(ctx context.Context, vmid int, stream bool) (io.ReadCloser, error) {
// // Initialize the stats script if it hasn't been initialized yet.
// initScriptErr, _ := n.statsScriptInitErrs.LoadOrCompute(vmid,
// func() (newValue error, cancel bool) {
// if err := n.lxcStatsScriptInit(ctx, vmid); err != nil {
// cancel = errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded)
// return err, cancel
// }
// return nil, false
// })
// if initScriptErr != nil {
// return nil, initScriptErr
// }
// if stream {
// return n.LXCCommand(ctx, vmid, statsStreamScript)
// }
// return n.LXCCommand(ctx, vmid, statsNonStreamScript)
// }