Back to Articles

gotop: Building a Braille-Powered System Monitor in Go

[ View on GitHub ]

gotop: Building a Braille-Powered System Monitor in Go

Hook

The entire Unicode specification includes exactly 256 Braille characters (U+2800 to U+28FF), each representing an 8-dot pattern. gotop exploits this to pack 8 vertical data points into a single terminal cell, achieving resolution impossible with ASCII art alone.

Context

Before the terminal renaissance of the 2010s, system administrators faced a stark choice: use sparse, text-based tools like top and htop for remote monitoring, or install full GUI applications that required X11 forwarding or physical access. This gap widened as developers increasingly worked on remote servers, Docker containers, and headless systems where graphical environments were impractical or impossible.

The Node.js ecosystem responded first with gtop and vtop, bringing colorful, dashboard-style monitoring to the terminal. But these solutions carried the weight of the entire Node runtime and npm dependency trees—hundreds of megabytes for a system monitor. gotop emerged in 2018 as a Go reimagining: a single static binary under 10MB that could be scp'd to any Linux or macOS machine and run immediately. It proved that beautiful terminal interfaces didn't require JavaScript, and that Go's standard library plus a handful of focused dependencies could deliver professional-grade TUI applications.

Technical Insight

Rendering

Metric Collection

CPU %

Usage MB

I/O rates

Process list

Braille graphs

Terminal output

Refresh trigger

Refresh trigger

Refresh trigger

Refresh trigger

Main Loop

termui Event Handler

CPU Poller

gopsutil

Memory Poller

gopsutil

Network Poller

gopsutil

Process Poller

gopsutil

Braille Canvas

drawille-go

Terminal Renderer

termui/termbox

System architecture — auto-generated

gotop's architecture centers on three technical decisions that define its character: Braille-based rendering for graph density, concurrent metric polling with channel-based coordination, and termui's immediate-mode rendering model.

The Braille rendering is gotop's most distinctive feature. Using the drawille-go library, it maps system metrics to Unicode Braille patterns. Each Braille character contains 8 dots in a 2x4 grid, meaning a single terminal cell can represent 256 different states. For a CPU usage graph 50 characters wide, you're actually plotting 100 horizontal data points (2 dots per character width) with 4 levels of vertical resolution per character height. This is why gotop's graphs look smoother than ASCII-based alternatives—it's literally operating at 8x the vertical resolution.

// Simplified example of how drawille-go maps data points to Braille
func plotCPUGraph(canvas *drawille.Canvas, data []float64) {
    for i, value := range data {
        // Map percentage (0-100) to vertical pixel position
        y := int((100 - value) * graphHeight / 100)
        // Each x position in data maps to multiple canvas pixels
        canvas.Set(i*2, y)     // Even pixel
        canvas.Set(i*2+1, y)   // Odd pixel for smoothing
    }
    // Canvas.String() converts the pixel matrix to Braille Unicode
    brailleOutput := canvas.String()
}

The second architectural pillar is concurrent metric gathering. gotop spawns goroutines for each monitoring domain—CPU, memory, disk, network—with each goroutine polling at its own optimal frequency. CPU metrics update every 1 second for responsiveness, while disk I/O polls every 3 seconds to reduce overhead. This is classic Go concurrency:

func (c *CPUWidget) Update() {
    go func() {
        ticker := time.NewTicker(1 * time.Second)
        for range ticker.C {
            percentages, _ := cpu.Percent(0, true) // per-CPU percentages
            c.dataChan <- percentages  // Non-blocking send to UI thread
        }
    }()
}

The data flows through buffered channels to the main rendering loop, which runs in the primary goroutine. This design prevents slow metric collection (like network latency on socket stats) from freezing the interface. The UI remains responsive even if one collector stalls.

The third piece is termui's widget system, which gotop extends. termui provides primitives like Block, Gauge, and List, but gotop adds LineGraph with Braille rendering and custom colorization. Each widget implements a Buffer() method that returns a grid of terminal cells with their content, colors, and attributes. The main event loop collects these buffers and diffs them against the previous frame, only redrawing changed cells—a terminal version of virtual DOM diffing.

gotop also implements vi-style navigation through termbox's event handling. When you press dd to kill a process, gotop's key handler maintains state across keystrokes, waiting for the second 'd' before invoking syscall.Kill(). This attention to detail—making the terminal feel as responsive as a native application—separates good TUIs from mere curses programs.

The final technical detail worth examining is gotop's use of gopsutil, a cross-platform library for system metrics. Rather than parsing /proc files or calling platform-specific APIs directly, gotop delegates to gopsutil, which handles the OS-specific implementation. This is why the same codebase works on Linux and macOS with minimal conditional compilation—the abstraction layer does the heavy lifting.

Gotcha

The elephant in the room: this repository is unmaintained. The README explicitly directs users to the xxxserxxx/gotop fork, which has continued development since 2020. Using the original cjbassi/gotop means no security patches, no bug fixes, and no support for newer kernel APIs or system metrics. This isn't a subtle deprecation—it's an explicit handoff.

Beyond the maintenance status, gotop has inherent limitations in the terminal environment. Braille rendering looks fantastic on modern terminal emulators with proper Unicode support, but breaks spectacularly on legacy terminals or incorrectly configured locales. The graphs degrade to garbage characters, and there's no graceful fallback to ASCII. Mouse support requires terminal emulator cooperation—it works perfectly in iTerm2 and GNOME Terminal but fails in screen/tmux without special configuration. The refresh rate is bounded by terminal rendering speed; on slow SSH connections with high latency, you'll see stuttering that no amount of Go optimization can fix. Finally, the process list widget shows only what the current user can see—monitoring system-wide processes requires running gotop as root, which many sysadmins avoid for security reasons.

Verdict

Use if: You're researching TUI architecture patterns in Go, studying how to leverage Unicode for terminal graphics, or need to understand the historical context before adopting the maintained fork at xxxserxxx/gotop. The original codebase is clean, well-structured, and serves as an excellent educational resource for learning termbox/termui programming. Skip if: You need a production system monitor—go directly to the xxxserxxx/gotop fork or consider btop++ for a more feature-rich alternative. Also skip if you're on Windows (no support), need sub-second refresh rates (terminal limitations), or work primarily in multiplexers like tmux where mouse support is inconsistent. The original gotop was brilliant for its time, but software requires maintenance, and the community has moved on.

// ADD TO YOUR README
[![Featured on Starlog](https://starlog.is/api/badge/developer-tools/cjbassi-gotop.svg)](https://starlog.is/api/badge-click/developer-tools/cjbassi-gotop)