mirror of
https://github.com/DeBrosOfficial/orama.git
synced 2026-03-17 15:46:58 +00:00
201 lines
5.1 KiB
Go
201 lines
5.1 KiB
Go
package report
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// collectSystem gathers system-level metrics using local commands and /proc files.
|
|
func collectSystem() *SystemReport {
|
|
r := &SystemReport{}
|
|
|
|
// 1. Uptime seconds: read /proc/uptime, parse first field
|
|
if data, err := os.ReadFile("/proc/uptime"); err == nil {
|
|
fields := strings.Fields(string(data))
|
|
if len(fields) >= 1 {
|
|
if f, err := strconv.ParseFloat(fields[0], 64); err == nil {
|
|
r.UptimeSeconds = int64(f)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 2. Uptime since: run `uptime -s`
|
|
{
|
|
ctx, cancel := context.WithTimeout(context.Background(), 4*time.Second)
|
|
defer cancel()
|
|
if out, err := runCmd(ctx, "uptime", "-s"); err == nil {
|
|
r.UptimeSince = strings.TrimSpace(out)
|
|
}
|
|
}
|
|
|
|
// 3. CPU count: run `nproc`
|
|
{
|
|
ctx, cancel := context.WithTimeout(context.Background(), 4*time.Second)
|
|
defer cancel()
|
|
if out, err := runCmd(ctx, "nproc"); err == nil {
|
|
if n, err := strconv.Atoi(strings.TrimSpace(out)); err == nil {
|
|
r.CPUCount = n
|
|
}
|
|
}
|
|
}
|
|
|
|
// 4. Load averages: read /proc/loadavg, parse first 3 fields
|
|
if data, err := os.ReadFile("/proc/loadavg"); err == nil {
|
|
fields := strings.Fields(string(data))
|
|
if len(fields) >= 3 {
|
|
if f, err := strconv.ParseFloat(fields[0], 64); err == nil {
|
|
r.LoadAvg1 = f
|
|
}
|
|
if f, err := strconv.ParseFloat(fields[1], 64); err == nil {
|
|
r.LoadAvg5 = f
|
|
}
|
|
if f, err := strconv.ParseFloat(fields[2], 64); err == nil {
|
|
r.LoadAvg15 = f
|
|
}
|
|
}
|
|
}
|
|
|
|
// 5 & 6. Memory and swap: run `free -m`, parse Mem: and Swap: lines
|
|
{
|
|
ctx, cancel := context.WithTimeout(context.Background(), 4*time.Second)
|
|
defer cancel()
|
|
if out, err := runCmd(ctx, "free", "-m"); err == nil {
|
|
for _, line := range strings.Split(out, "\n") {
|
|
fields := strings.Fields(line)
|
|
if len(fields) >= 4 && fields[0] == "Mem:" {
|
|
// Mem: total used free shared buff/cache available
|
|
if n, err := strconv.Atoi(fields[1]); err == nil {
|
|
r.MemTotalMB = n
|
|
}
|
|
if n, err := strconv.Atoi(fields[2]); err == nil {
|
|
r.MemUsedMB = n
|
|
}
|
|
if n, err := strconv.Atoi(fields[3]); err == nil {
|
|
r.MemFreeMB = n
|
|
}
|
|
if len(fields) >= 7 {
|
|
if n, err := strconv.Atoi(fields[6]); err == nil {
|
|
r.MemAvailMB = n
|
|
}
|
|
}
|
|
if r.MemTotalMB > 0 {
|
|
r.MemUsePct = (r.MemTotalMB - r.MemAvailMB) * 100 / r.MemTotalMB
|
|
}
|
|
}
|
|
if len(fields) >= 3 && fields[0] == "Swap:" {
|
|
if n, err := strconv.Atoi(fields[1]); err == nil {
|
|
r.SwapTotalMB = n
|
|
}
|
|
if n, err := strconv.Atoi(fields[2]); err == nil {
|
|
r.SwapUsedMB = n
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 7. Disk usage: run `df -h /` and `df -h /opt/orama`, use whichever has higher usage
|
|
{
|
|
type diskInfo struct {
|
|
total string
|
|
used string
|
|
avail string
|
|
usePct int
|
|
}
|
|
|
|
parseDf := func(out string) *diskInfo {
|
|
lines := strings.Split(out, "\n")
|
|
if len(lines) < 2 {
|
|
return nil
|
|
}
|
|
fields := strings.Fields(lines[1])
|
|
if len(fields) < 5 {
|
|
return nil
|
|
}
|
|
pctStr := strings.TrimSuffix(fields[4], "%")
|
|
pct, err := strconv.Atoi(pctStr)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return &diskInfo{
|
|
total: fields[1],
|
|
used: fields[2],
|
|
avail: fields[3],
|
|
usePct: pct,
|
|
}
|
|
}
|
|
|
|
ctx1, cancel1 := context.WithTimeout(context.Background(), 4*time.Second)
|
|
defer cancel1()
|
|
rootDisk := (*diskInfo)(nil)
|
|
if out, err := runCmd(ctx1, "df", "-h", "/"); err == nil {
|
|
rootDisk = parseDf(out)
|
|
}
|
|
|
|
ctx2, cancel2 := context.WithTimeout(context.Background(), 4*time.Second)
|
|
defer cancel2()
|
|
optDisk := (*diskInfo)(nil)
|
|
if out, err := runCmd(ctx2, "df", "-h", "/opt/orama"); err == nil {
|
|
optDisk = parseDf(out)
|
|
}
|
|
|
|
best := rootDisk
|
|
if optDisk != nil && (best == nil || optDisk.usePct > best.usePct) {
|
|
best = optDisk
|
|
}
|
|
if best != nil {
|
|
r.DiskTotalGB = best.total
|
|
r.DiskUsedGB = best.used
|
|
r.DiskAvailGB = best.avail
|
|
r.DiskUsePct = best.usePct
|
|
}
|
|
}
|
|
|
|
// 8. Inode usage: run `df -i /`, parse Use% from second line
|
|
{
|
|
ctx, cancel := context.WithTimeout(context.Background(), 4*time.Second)
|
|
defer cancel()
|
|
if out, err := runCmd(ctx, "df", "-i", "/"); err == nil {
|
|
lines := strings.Split(out, "\n")
|
|
if len(lines) >= 2 {
|
|
fields := strings.Fields(lines[1])
|
|
if len(fields) >= 5 {
|
|
pctStr := strings.TrimSuffix(fields[4], "%")
|
|
if n, err := strconv.Atoi(pctStr); err == nil {
|
|
r.InodePct = n
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 9. OOM kills: run `dmesg 2>/dev/null | grep -ci 'out of memory'` via bash -c
|
|
{
|
|
ctx, cancel := context.WithTimeout(context.Background(), 4*time.Second)
|
|
defer cancel()
|
|
if out, err := runCmd(ctx, "bash", "-c", "dmesg 2>/dev/null | grep -ci 'out of memory'"); err == nil {
|
|
if n, err := strconv.Atoi(strings.TrimSpace(out)); err == nil {
|
|
r.OOMKills = n
|
|
}
|
|
}
|
|
// On error, OOMKills stays 0 (zero value)
|
|
}
|
|
|
|
// 10. Kernel version: run `uname -r`
|
|
{
|
|
ctx, cancel := context.WithTimeout(context.Background(), 4*time.Second)
|
|
defer cancel()
|
|
if out, err := runCmd(ctx, "uname", "-r"); err == nil {
|
|
r.KernelVersion = strings.TrimSpace(out)
|
|
}
|
|
}
|
|
|
|
// 11. Current unix timestamp
|
|
r.TimeUnix = time.Now().Unix()
|
|
|
|
return r
|
|
}
|