1
0
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:
Noah Qin
2026-03-03 16:05:55 +08:00
committed by GitHub
parent 87bf90f712
commit 2a4eaf007b
2 changed files with 125 additions and 92 deletions

View File

@@ -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()
}
}

View File

@@ -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 {