mirror of
https://github.com/yusing/godoxy.git
synced 2026-03-24 18:11:19 +01:00
169 lines
5.0 KiB
Go
169 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")
|
|
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)
|
|
// }
|