234 lines
5.0 KiB
Go

package stats
import (
"context"
"fmt"
"log"
"strconv"
"strings"
"sync"
"time"
"github.com/gofiber/contrib/websocket"
"rul.sh/vaulterm/lib"
)
func HandleSSHStats(c *websocket.Conn, client *lib.SSHClient) error {
if err := client.Connect(); err != nil {
log.Printf("error connecting to SSH: %v", err)
return err
}
defer client.Close()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
msgCh := make(chan string)
go func() {
for {
select {
case <-ctx.Done():
return
default:
wg := &sync.WaitGroup{}
wg.Add(5)
go getCPUUsage(client, wg, msgCh)
go getMemoryUsage(client, wg, msgCh)
go getDiskUsage(client, wg, msgCh)
go getNetworkUsage(client, wg, msgCh)
go getUptime(client, wg, msgCh)
wg.Wait()
}
}
}()
go func() {
for msg := range msgCh {
if err := c.WriteMessage(websocket.TextMessage, []byte(msg)); err != nil {
break
}
}
}()
for {
_, _, err := c.ReadMessage()
if err != nil {
break
}
}
return nil
}
func getCPUUsage(client *lib.SSHClient, wg *sync.WaitGroup, result chan<- string) {
defer wg.Done()
cmd := "cat /proc/stat | grep '^cpu '"
cpuData, err := client.Exec(cmd)
if err != nil {
return
}
total1, idle1, err := parseCPUStats(cpuData)
if err != nil {
return
}
time.Sleep(time.Second)
cpuData, err = client.Exec(cmd)
if err != nil {
return
}
total2, idle2, err := parseCPUStats(cpuData)
if err != nil {
return
}
totalDiff := total2 - total1
idleDiff := idle2 - idle1
usage := (float64(totalDiff-idleDiff) / float64(totalDiff)) * 100
result <- fmt.Sprintf("\x01%.2f", usage)
}
func parseCPUStats(data string) (int64, int64, error) {
fields := strings.Fields(data)
if len(fields) < 8 {
return 0, 0, fmt.Errorf("unexpected format in /proc/stat")
}
user, _ := strconv.ParseInt(fields[1], 10, 64)
nice, _ := strconv.ParseInt(fields[2], 10, 64)
system, _ := strconv.ParseInt(fields[3], 10, 64)
idle, _ := strconv.ParseInt(fields[4], 10, 64)
iowait, _ := strconv.ParseInt(fields[5], 10, 64)
irq, _ := strconv.ParseInt(fields[6], 10, 64)
softirq, _ := strconv.ParseInt(fields[7], 10, 64)
total := user + nice + system + idle + iowait + irq + softirq
idle = idle + iowait
return total, idle, nil
}
func getMemoryUsage(client *lib.SSHClient, wg *sync.WaitGroup, result chan<- string) {
defer wg.Done()
data, err := client.Exec("cat /proc/meminfo")
if err != nil {
return
}
var total, available int
lines := strings.Split(data, "\n")
for _, line := range lines {
line = strings.TrimSpace(strings.ToLower(line))
fields := strings.Fields(line)
if len(fields) < 2 {
continue
}
value, _ := strconv.Atoi(fields[1])
if strings.HasPrefix(line, "memtotal") {
total = value / 1024
} else if strings.HasPrefix(line, "memavailable") {
available = value / 1024
}
}
result <- fmt.Sprintf("\x02%d,%d", total, available)
}
func getDiskUsage(client *lib.SSHClient, wg *sync.WaitGroup, result chan<- string) {
defer wg.Done()
data, err := client.Exec("df -h /")
if err != nil {
return
}
lines := strings.Split(data, "\n")
if len(lines) < 2 {
return
}
fields := strings.Fields(lines[1])
result <- fmt.Sprintf("\x03%s,%s,%s", fields[1], fields[2], fields[4])
}
func getNetworkUsage(client *lib.SSHClient, wg *sync.WaitGroup, result chan<- string) {
defer wg.Done()
cmd := `iface=$(ip route | awk '/^default/ {print $5}'); if [ -n "$iface" ]; then ip -s link show "$iface"; fi`
data, err := client.Exec(cmd)
if err != nil || strings.TrimSpace(data) == "" {
return
}
// Parse RX/TX values from the network data
rx, tx := parseNetwork(data)
result <- fmt.Sprintf("\x04%d,%d", rx/1024/1024, tx/1024/1024)
}
func parseNetwork(data string) (int, int) {
lines := strings.Split(data, "\n")
var rxBytes, txBytes int
rxMode, txMode := false, false
for _, line := range lines {
line := strings.TrimSpace(line)
// Check for RX and TX headers
if strings.HasPrefix(line, "RX:") {
rxMode = true
txMode = false
continue
}
if strings.HasPrefix(line, "TX:") {
txMode = true
rxMode = false
continue
}
// Parse RX bytes if in RX mode
if rxMode {
fields := strings.Fields(line)
if len(fields) > 0 {
rxBytes, _ = strconv.Atoi(fields[0])
}
rxMode = false // Reset RX mode after capturing data
}
// Parse TX bytes if in TX mode
if txMode {
fields := strings.Fields(line)
if len(fields) > 0 {
txBytes, _ = strconv.Atoi(fields[0])
}
txMode = false // Reset TX mode after capturing data
}
}
return txBytes, rxBytes
}
func getUptime(client *lib.SSHClient, wg *sync.WaitGroup, result chan<- string) {
defer wg.Done()
// Try to read uptime from /proc/uptime
data, err := client.Exec("cat /proc/uptime")
if err != nil {
return
}
data = strings.TrimSpace(data)
uptimeParts := strings.Split(data, " ")
if len(uptimeParts) < 1 {
return
}
uptimeSeconds, err := strconv.ParseFloat(uptimeParts[0], 64)
if err != nil {
return
}
result <- fmt.Sprintf("\x05%d", int(uptimeSeconds))
}