Skip to content

Commit af7e892

Browse files
committed
Added layouts and resizing support
1 parent 235fc42 commit af7e892

File tree

3 files changed

+100
-34
lines changed

3 files changed

+100
-34
lines changed

README.md

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,30 @@
11
# goasitop
22

3-
`goasitop` is a terminal-based monitoring tool "top" designed to display real-time power metrics for Apple Silicon chips. It provides a simple and efficient way to monitor CPU and GPU usage, E-Cores and P-Cores, power consumption, and other system metrics directly from your terminal!
3+
`goasitop` is a terminal-based monitoring tool "top" designed to display real-time metrics for Apple Silicon chips. It provides a simple and efficient way to monitor CPU and GPU usage, E-Cores and P-Cores, power consumption, and other system metrics directly from your terminal!
44

5-
![Screenshot](screenshot.png)
5+
![goasitop](screenshot2.png)
6+
7+
## Compatibility
8+
9+
- Apple Silicon Only (ARM64)
10+
- macOS Monterey 12.3+
611

712
## Features
813

914
- Apple Silicon Monitor Top written in Go Lang (Under 1,000 lines of code)
1015
- Real-time CPU and GPU power usage display.
1116
- Detailed metrics for different CPU clusters (E-Cores and P-Cores).
1217
- Memory usage and swap information.
13-
- Network usage information and Disk Activity Read/Write
18+
- Network usage information
19+
- Disk Activity Read/Write
1420
- Easy-to-read terminal UI
21+
- Two layouts: default and alternative
1522
- Support for all Apple Silicon models.
1623

1724
## Install via Homebrew
1825

26+
Help get us on the official Homebrew formulas by giving us a star! [goasitop](https://github.com/context-labs/goasitop)
27+
1928
```bash
2029
brew tap context-labs/goasitop https://github.com/context-labs/goasitop
2130
```
@@ -60,6 +69,7 @@ After installation, you can start `goasitop` by simply running:
6069
Use the following keys to interact with the application:
6170
- `q`: Quit the application.
6271
- `r`: Refresh the UI data manually.
72+
- `l`: Toggle the current layout.
6373

6474
## Contributing
6575

@@ -71,6 +81,13 @@ Contributions are what make the open-source community such an amazing place to l
7181
4. Push to the Branch (`git push origin feature/AmazingFeature`)
7282
5. Open a Pull Request
7383

84+
## What does goasitop use to get real-time data?
85+
86+
- `sysctl`: For CPU model information
87+
- `system_profiler`: For GPU Core Count
88+
- `psutil`: For memory and swap metrics
89+
- `powermetrics`: For majority of CPU, GPU, Network, and Disk metrics
90+
7491
## License
7592

7693
Distributed under the MIT License. See `LICENSE` for more information.

main.go

Lines changed: 80 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ var (
8383
modelText, PowerChart, NetworkInfo, ProcessInfo *w.Paragraph
8484
grid *ui.Grid
8585

86-
stderrLogger = log.New(os.Stderr, "", 0)
86+
stderrLogger = log.New(os.Stderr, "", 0)
87+
currentGridLayout = "default"
8788
)
8889

8990
func setupUI() {
@@ -173,23 +174,78 @@ func setupGrid() {
173174
// Define the rows and columns
174175
grid.Set(
175176
ui.NewRow(1.0/2, // This row now takes half the height of the grid
176-
ui.NewCol(1.0/2, ui.NewRow(1.0, cpu1Gauge)), // ui.NewCol(1.0, ui.NewRow(1.0, cpu2Gauge))),
177-
ui.NewCol(1.0/2, ui.NewRow(1.0, cpu2Gauge)), // ProcessInfo spans this entire column
177+
ui.NewCol(1.0/2, ui.NewRow(1.0/2, cpu1Gauge), ui.NewCol(1.0, ui.NewRow(1.0, cpu2Gauge))),
178+
ui.NewCol(1.0/2, ui.NewRow(1.0/2, gpuGauge), ui.NewCol(1.0, ui.NewRow(1.0, aneGauge))), // ui.NewCol(1.0/2, ui.NewRow(1.0, ProcessInfo)), // ProcessInfo spans this entire column
178179
),
179180
ui.NewRow(1.0/4,
180-
ui.NewCol(1.0/4, gpuGauge),
181-
ui.NewCol(1.0/4, aneGauge),
181+
ui.NewCol(1.0/4, modelText),
182+
ui.NewCol(1.0/4, NetworkInfo),
182183
ui.NewCol(1.0/4, PowerChart),
183184
ui.NewCol(1.0/4, TotalPowerChart),
184185
),
185186
ui.NewRow(1.0/4,
186-
ui.NewCol(3.0/6, memoryGauge),
187-
ui.NewCol(1.0/6, modelText),
188-
ui.NewCol(2.0/6, NetworkInfo),
187+
ui.NewCol(1.0, memoryGauge),
189188
),
190189
)
191190
}
192191

192+
func switchGridLayout() {
193+
194+
if currentGridLayout == "default" {
195+
ui.Clear()
196+
newGrid := ui.NewGrid()
197+
newGrid.Set(
198+
ui.NewRow(1.0/2, // This row now takes half the height of the grid
199+
ui.NewCol(1.0/2, ui.NewRow(1.0, cpu1Gauge)), // ui.NewCol(1.0, ui.NewRow(1.0, cpu2Gauge))),
200+
ui.NewCol(1.0/2, ui.NewRow(1.0, cpu2Gauge)), // ProcessInfo spans this entire column
201+
),
202+
ui.NewRow(1.0/4,
203+
ui.NewCol(1.0/4, gpuGauge),
204+
ui.NewCol(1.0/4, aneGauge),
205+
ui.NewCol(1.0/4, PowerChart),
206+
ui.NewCol(1.0/4, TotalPowerChart),
207+
),
208+
ui.NewRow(1.0/4,
209+
ui.NewCol(3.0/6, memoryGauge),
210+
ui.NewCol(1.0/6, modelText),
211+
ui.NewCol(2.0/6, NetworkInfo),
212+
),
213+
)
214+
215+
// Set the new grid's dimensions to match the terminal size
216+
termWidth, termHeight := ui.TerminalDimensions()
217+
newGrid.SetRect(0, 0, termWidth, termHeight)
218+
grid = newGrid
219+
currentGridLayout = "alternative"
220+
ui.Render(grid)
221+
} else {
222+
ui.Clear()
223+
newGrid := ui.NewGrid()
224+
225+
newGrid.Set(
226+
ui.NewRow(1.0/2, // This row now takes half the height of the grid
227+
ui.NewCol(1.0/2, ui.NewRow(1.0/2, cpu1Gauge), ui.NewCol(1.0, ui.NewRow(1.0, cpu2Gauge))),
228+
ui.NewCol(1.0/2, ui.NewRow(1.0/2, gpuGauge), ui.NewCol(1.0, ui.NewRow(1.0, aneGauge))), // ui.NewCol(1.0/2, ui.NewRow(1.0, ProcessInfo)), // ProcessInfo spans this entire column
229+
),
230+
ui.NewRow(1.0/4,
231+
ui.NewCol(1.0/4, modelText),
232+
ui.NewCol(1.0/4, NetworkInfo),
233+
ui.NewCol(1.0/4, PowerChart),
234+
ui.NewCol(1.0/4, TotalPowerChart),
235+
),
236+
ui.NewRow(1.0/4,
237+
ui.NewCol(1.0, memoryGauge),
238+
),
239+
)
240+
// Set the new grid's dimensions to match the terminal size
241+
termWidth, termHeight := ui.TerminalDimensions()
242+
newGrid.SetRect(0, 0, termWidth, termHeight)
243+
grid = newGrid
244+
currentGridLayout = "default"
245+
ui.Render(grid)
246+
}
247+
}
248+
193249
func StderrToLogfile(logfile *os.File) {
194250
syscall.Dup2(int(logfile.Fd()), 2)
195251
}
@@ -264,8 +320,22 @@ func main() {
264320
ui.Close()
265321
os.Exit(0)
266322
return
323+
case "<Resize>":
324+
payload := e.Payload.(ui.Resize)
325+
grid.SetRect(0, 0, payload.Width, payload.Height)
326+
ui.Render(grid)
267327
case "r":
268328
// refresh ui data
329+
termWidth, termHeight := ui.TerminalDimensions()
330+
grid.SetRect(0, 0, termWidth, termHeight)
331+
ui.Clear()
332+
ui.Render(grid)
333+
case "l":
334+
// Set the new grid's dimensions to match the terminal size
335+
termWidth, termHeight := ui.TerminalDimensions()
336+
grid.SetRect(0, 0, termWidth, termHeight)
337+
ui.Clear()
338+
switchGridLayout()
269339
ui.Render(grid)
270340
}
271341
case <-done:
@@ -374,9 +444,9 @@ func updateCPUUI(cpuMetrics CPUMetrics) {
374444
aneGauge.Title = fmt.Sprintf("ANE Usage: %d%% @ %.1f W", aneUtil, cpuMetrics.ANEW)
375445
aneGauge.Percent = aneUtil
376446

377-
TotalPowerChart.Title = fmt.Sprintf("%.1f W Total Power Usage", cpuMetrics.PackageW)
447+
TotalPowerChart.Title = fmt.Sprintf("%.1f W Total Power", cpuMetrics.PackageW)
378448
PowerChart.Title = fmt.Sprintf("%.1f W CPU - %.1f W GPU", cpuMetrics.CPUW, cpuMetrics.GPUW)
379-
PowerChart.Text = fmt.Sprintf("\nCPU Power: %.1f W\nGPU Power: %.1f W\nANE Power: %.1f W\nTotal Power: %.1f W", cpuMetrics.CPUW, cpuMetrics.GPUW, cpuMetrics.ANEW, cpuMetrics.PackageW)
449+
PowerChart.Text = fmt.Sprintf("CPU Power: %.1f W\nGPU Power: %.1f W\nANE Power: %.1f W\nTotal Power: %.1f W", cpuMetrics.CPUW, cpuMetrics.GPUW, cpuMetrics.ANEW, cpuMetrics.PackageW)
380450

381451
memoryMetrics := getMemoryMetrics()
382452

@@ -560,11 +630,6 @@ func parseCPUMetrics(powermetricsOutput string, cpuMetrics CPUMetrics) CPUMetric
560630
} else if strings.HasPrefix(cluster, "P") {
561631
pClusterFreqTotal += int(freqMHz)
562632
cpuMetrics.PClusterFreqMHz = pClusterFreqTotal
563-
// pClusterFreqReadings = append(pClusterFreqReadings, freqMHz)
564-
// if len(pClusterFreqReadings) > numReadings {
565-
// pClusterFreqReadings = pClusterFreqReadings[1:] // Remove the oldest reading
566-
// }
567-
// cpuMetrics.PClusterFreqMHz = average(pClusterFreqReadings)
568633
}
569634
}
570635

@@ -624,14 +689,6 @@ func parseCPUMetrics(powermetricsOutput string, cpuMetrics CPUMetrics) CPUMetric
624689
cpuMetrics.PClusterFreqMHz = max(cpuMetrics.P0ClusterFreqMHz, cpuMetrics.P1ClusterFreqMHz)
625690
}
626691

627-
// // Calculate average P-Cluster Active using a moving average
628-
// currentPActive := (cpuMetrics.P0ClusterActive + cpuMetrics.P1ClusterActive + cpuMetrics.P2ClusterActive + cpuMetrics.P3ClusterActive) / 4
629-
// pClusterReadings = append(pClusterReadings, currentPActive)
630-
// if len(pClusterReadings) > numReadings {
631-
// pClusterReadings = pClusterReadings[1:] // Remove the oldest reading
632-
// }
633-
// cpuMetrics.PClusterActive = average(pClusterReadings)
634-
635692
// Calculate average active residency and frequency for E and P clusters
636693
if eClusterCount > 0 {
637694
cpuMetrics.EClusterActive = eClusterActiveTotal / eClusterCount
@@ -640,14 +697,6 @@ func parseCPUMetrics(powermetricsOutput string, cpuMetrics CPUMetrics) CPUMetric
640697
return cpuMetrics
641698
}
642699

643-
func average(nums []int) int {
644-
sum := 0
645-
for _, num := range nums {
646-
sum += num
647-
}
648-
return sum / len(nums)
649-
}
650-
651700
func max(nums ...int) int {
652701
maxVal := nums[0]
653702
for _, num := range nums[1:] {

screenshot2.png

1.42 MB
Loading

0 commit comments

Comments
 (0)