mirror of
https://github.com/tw93/Mole.git
synced 2026-03-22 20:15:07 +00:00
feat(status): add --json flag for programmatic access (#529)
Add --json command-line flag to mo status that outputs system metrics in JSON format without requiring a TTY environment. This enables: - Integration with GUI applications (e.g., native macOS apps) - Use in automation scripts and monitoring systems - Piping output to tools like jq for data extraction - Recording metrics for analysis Implementation: - Add JSON struct tags to all metric types - Add --json flag using Go's flag package - Implement runJSONMode() for one-time metric collection - Refactor main() to support both TUI and JSON modes - Maintain 100% backward compatibility (default TUI unchanged) Testing: - All 454 existing tests pass - JSON output validated with jq and python json.tool - Pipeline and redirection work correctly - No breaking changes to existing functionality
This commit is contained in:
@@ -2,6 +2,8 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -17,6 +19,9 @@ const refreshInterval = time.Second
|
|||||||
var (
|
var (
|
||||||
Version = "dev"
|
Version = "dev"
|
||||||
BuildTime = ""
|
BuildTime = ""
|
||||||
|
|
||||||
|
// Command-line flags
|
||||||
|
jsonOutput = flag.Bool("json", false, "output metrics as JSON instead of TUI")
|
||||||
)
|
)
|
||||||
|
|
||||||
type tickMsg struct{}
|
type tickMsg struct{}
|
||||||
@@ -204,10 +209,38 @@ func animTickWithSpeed(cpuUsage float64) tea.Cmd {
|
|||||||
return tea.Tick(time.Duration(interval)*time.Millisecond, func(time.Time) tea.Msg { return animTickMsg{} })
|
return tea.Tick(time.Duration(interval)*time.Millisecond, func(time.Time) tea.Msg { return animTickMsg{} })
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
// runJSONMode collects metrics once and outputs as JSON.
|
||||||
|
func runJSONMode() {
|
||||||
|
collector := NewCollector()
|
||||||
|
data, err := collector.Collect()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "error collecting metrics: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
encoder := json.NewEncoder(os.Stdout)
|
||||||
|
encoder.SetIndent("", " ")
|
||||||
|
if err := encoder.Encode(data); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "error encoding JSON: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// runTUIMode runs the interactive terminal UI.
|
||||||
|
func runTUIMode() {
|
||||||
p := tea.NewProgram(newModel(), tea.WithAltScreen())
|
p := tea.NewProgram(newModel(), tea.WithAltScreen())
|
||||||
if _, err := p.Run(); err != nil {
|
if _, err := p.Run(); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "system status error: %v\n", err)
|
fmt.Fprintf(os.Stderr, "system status error: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if *jsonOutput {
|
||||||
|
runJSONMode()
|
||||||
|
} else {
|
||||||
|
runTUIMode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -57,143 +57,143 @@ func (rb *RingBuffer) Slice() []float64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type MetricsSnapshot struct {
|
type MetricsSnapshot struct {
|
||||||
CollectedAt time.Time
|
CollectedAt time.Time `json:"collected_at"`
|
||||||
Host string
|
Host string `json:"host"`
|
||||||
Platform string
|
Platform string `json:"platform"`
|
||||||
Uptime string
|
Uptime string `json:"uptime"`
|
||||||
Procs uint64
|
Procs uint64 `json:"procs"`
|
||||||
Hardware HardwareInfo
|
Hardware HardwareInfo `json:"hardware"`
|
||||||
HealthScore int // 0-100 system health score
|
HealthScore int `json:"health_score"` // 0-100 system health score
|
||||||
HealthScoreMsg string // Brief explanation
|
HealthScoreMsg string `json:"health_score_msg"` // Brief explanation
|
||||||
|
|
||||||
CPU CPUStatus
|
CPU CPUStatus `json:"cpu"`
|
||||||
GPU []GPUStatus
|
GPU []GPUStatus `json:"gpu"`
|
||||||
Memory MemoryStatus
|
Memory MemoryStatus `json:"memory"`
|
||||||
Disks []DiskStatus
|
Disks []DiskStatus `json:"disks"`
|
||||||
DiskIO DiskIOStatus
|
DiskIO DiskIOStatus `json:"disk_io"`
|
||||||
Network []NetworkStatus
|
Network []NetworkStatus `json:"network"`
|
||||||
NetworkHistory NetworkHistory
|
NetworkHistory NetworkHistory `json:"network_history"`
|
||||||
Proxy ProxyStatus
|
Proxy ProxyStatus `json:"proxy"`
|
||||||
Batteries []BatteryStatus
|
Batteries []BatteryStatus `json:"batteries"`
|
||||||
Thermal ThermalStatus
|
Thermal ThermalStatus `json:"thermal"`
|
||||||
Sensors []SensorReading
|
Sensors []SensorReading `json:"sensors"`
|
||||||
Bluetooth []BluetoothDevice
|
Bluetooth []BluetoothDevice `json:"bluetooth"`
|
||||||
TopProcesses []ProcessInfo
|
TopProcesses []ProcessInfo `json:"top_processes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type HardwareInfo struct {
|
type HardwareInfo struct {
|
||||||
Model string // MacBook Pro 14-inch, 2021
|
Model string `json:"model"` // MacBook Pro 14-inch, 2021
|
||||||
CPUModel string // Apple M1 Pro / Intel Core i7
|
CPUModel string `json:"cpu_model"` // Apple M1 Pro / Intel Core i7
|
||||||
TotalRAM string // 16GB
|
TotalRAM string `json:"total_ram"` // 16GB
|
||||||
DiskSize string // 512GB
|
DiskSize string `json:"disk_size"` // 512GB
|
||||||
OSVersion string // macOS Sonoma 14.5
|
OSVersion string `json:"os_version"` // macOS Sonoma 14.5
|
||||||
RefreshRate string // 120Hz / 60Hz
|
RefreshRate string `json:"refresh_rate"` // 120Hz / 60Hz
|
||||||
}
|
}
|
||||||
|
|
||||||
type DiskIOStatus struct {
|
type DiskIOStatus struct {
|
||||||
ReadRate float64 // MB/s
|
ReadRate float64 `json:"read_rate"` // MB/s
|
||||||
WriteRate float64 // MB/s
|
WriteRate float64 `json:"write_rate"` // MB/s
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProcessInfo struct {
|
type ProcessInfo struct {
|
||||||
Name string
|
Name string `json:"name"`
|
||||||
CPU float64
|
CPU float64 `json:"cpu"`
|
||||||
Memory float64
|
Memory float64 `json:"memory"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CPUStatus struct {
|
type CPUStatus struct {
|
||||||
Usage float64
|
Usage float64 `json:"usage"`
|
||||||
PerCore []float64
|
PerCore []float64 `json:"per_core"`
|
||||||
PerCoreEstimated bool
|
PerCoreEstimated bool `json:"per_core_estimated"`
|
||||||
Load1 float64
|
Load1 float64 `json:"load1"`
|
||||||
Load5 float64
|
Load5 float64 `json:"load5"`
|
||||||
Load15 float64
|
Load15 float64 `json:"load15"`
|
||||||
CoreCount int
|
CoreCount int `json:"core_count"`
|
||||||
LogicalCPU int
|
LogicalCPU int `json:"logical_cpu"`
|
||||||
PCoreCount int // Performance cores (Apple Silicon)
|
PCoreCount int `json:"p_core_count"` // Performance cores (Apple Silicon)
|
||||||
ECoreCount int // Efficiency cores (Apple Silicon)
|
ECoreCount int `json:"e_core_count"` // Efficiency cores (Apple Silicon)
|
||||||
}
|
}
|
||||||
|
|
||||||
type GPUStatus struct {
|
type GPUStatus struct {
|
||||||
Name string
|
Name string `json:"name"`
|
||||||
Usage float64
|
Usage float64 `json:"usage"`
|
||||||
MemoryUsed float64
|
MemoryUsed float64 `json:"memory_used"`
|
||||||
MemoryTotal float64
|
MemoryTotal float64 `json:"memory_total"`
|
||||||
CoreCount int
|
CoreCount int `json:"core_count"`
|
||||||
Note string
|
Note string `json:"note"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type MemoryStatus struct {
|
type MemoryStatus struct {
|
||||||
Used uint64
|
Used uint64 `json:"used"`
|
||||||
Total uint64
|
Total uint64 `json:"total"`
|
||||||
UsedPercent float64
|
UsedPercent float64 `json:"used_percent"`
|
||||||
SwapUsed uint64
|
SwapUsed uint64 `json:"swap_used"`
|
||||||
SwapTotal uint64
|
SwapTotal uint64 `json:"swap_total"`
|
||||||
Cached uint64 // File cache that can be freed if needed
|
Cached uint64 `json:"cached"` // File cache that can be freed if needed
|
||||||
Pressure string // macOS memory pressure: normal/warn/critical
|
Pressure string `json:"pressure"` // macOS memory pressure: normal/warn/critical
|
||||||
}
|
}
|
||||||
|
|
||||||
type DiskStatus struct {
|
type DiskStatus struct {
|
||||||
Mount string
|
Mount string `json:"mount"`
|
||||||
Device string
|
Device string `json:"device"`
|
||||||
Used uint64
|
Used uint64 `json:"used"`
|
||||||
Total uint64
|
Total uint64 `json:"total"`
|
||||||
UsedPercent float64
|
UsedPercent float64 `json:"used_percent"`
|
||||||
Fstype string
|
Fstype string `json:"fstype"`
|
||||||
External bool
|
External bool `json:"external"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type NetworkStatus struct {
|
type NetworkStatus struct {
|
||||||
Name string
|
Name string `json:"name"`
|
||||||
RxRateMBs float64
|
RxRateMBs float64 `json:"rx_rate_mbs"`
|
||||||
TxRateMBs float64
|
TxRateMBs float64 `json:"tx_rate_mbs"`
|
||||||
IP string
|
IP string `json:"ip"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NetworkHistory holds the global network usage history.
|
// NetworkHistory holds the global network usage history.
|
||||||
type NetworkHistory struct {
|
type NetworkHistory struct {
|
||||||
RxHistory []float64
|
RxHistory []float64 `json:"rx_history"`
|
||||||
TxHistory []float64
|
TxHistory []float64 `json:"tx_history"`
|
||||||
}
|
}
|
||||||
|
|
||||||
const NetworkHistorySize = 120 // Increased history size for wider graph
|
const NetworkHistorySize = 120 // Increased history size for wider graph
|
||||||
|
|
||||||
type ProxyStatus struct {
|
type ProxyStatus struct {
|
||||||
Enabled bool
|
Enabled bool `json:"enabled"`
|
||||||
Type string // HTTP, HTTPS, SOCKS, PAC, WPAD, TUN
|
Type string `json:"type"` // HTTP, HTTPS, SOCKS, PAC, WPAD, TUN
|
||||||
Host string
|
Host string `json:"host"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type BatteryStatus struct {
|
type BatteryStatus struct {
|
||||||
Percent float64
|
Percent float64 `json:"percent"`
|
||||||
Status string
|
Status string `json:"status"`
|
||||||
TimeLeft string
|
TimeLeft string `json:"time_left"`
|
||||||
Health string
|
Health string `json:"health"`
|
||||||
CycleCount int
|
CycleCount int `json:"cycle_count"`
|
||||||
Capacity int // Maximum capacity percentage (e.g., 85 means 85% of original)
|
Capacity int `json:"capacity"` // Maximum capacity percentage (e.g., 85 means 85% of original)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ThermalStatus struct {
|
type ThermalStatus struct {
|
||||||
CPUTemp float64
|
CPUTemp float64 `json:"cpu_temp"`
|
||||||
GPUTemp float64
|
GPUTemp float64 `json:"gpu_temp"`
|
||||||
FanSpeed int
|
FanSpeed int `json:"fan_speed"`
|
||||||
FanCount int
|
FanCount int `json:"fan_count"`
|
||||||
SystemPower float64 // System power consumption in Watts
|
SystemPower float64 `json:"system_power"` // System power consumption in Watts
|
||||||
AdapterPower float64 // AC adapter max power in Watts
|
AdapterPower float64 `json:"adapter_power"` // AC adapter max power in Watts
|
||||||
BatteryPower float64 // Battery charge/discharge power in Watts (positive = discharging)
|
BatteryPower float64 `json:"battery_power"` // Battery charge/discharge power in Watts (positive = discharging)
|
||||||
}
|
}
|
||||||
|
|
||||||
type SensorReading struct {
|
type SensorReading struct {
|
||||||
Label string
|
Label string `json:"label"`
|
||||||
Value float64
|
Value float64 `json:"value"`
|
||||||
Unit string
|
Unit string `json:"unit"`
|
||||||
Note string
|
Note string `json:"note"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type BluetoothDevice struct {
|
type BluetoothDevice struct {
|
||||||
Name string
|
Name string `json:"name"`
|
||||||
Connected bool
|
Connected bool `json:"connected"`
|
||||||
Battery string
|
Battery string `json:"battery"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Collector struct {
|
type Collector struct {
|
||||||
|
|||||||
Reference in New Issue
Block a user