A Console Routine on Channeling NATS. Let's GO!
TL;DR
This article is a short example of NATS - an open source messaging system, TVIEW - Golang based Terminal UI library, concurrency in Golang using Channels and Goroutines. The source code can be found on Github.
What
I have recently started working on a personal Golang project to develop something similar to Uptime Kuma which is a self-hosted monitoring tool. I believe it’s a perfect project with the right complexity to learn advanced concepts of Go.
The project code in Github has 4 directories:
- Server - The central server which stores servers & resource details, configs etc
- Client - TUI based native client app to manage resources that need to be monitored. Also gives TUI based views into historical/real-time monitoring data.
- Agent - Remote light-weight agent running on target host servers responsible for shipping telemetry data to the central server
- Proto - Protobuf based API contracts between server, client & agent.
NATS
NATS is a simple, secure and performant communications system and data layer for digital systems, services and devices. It’s used in this example project for pub-sub messaging (shipping) telemetry data from target remote servers to the central server.
The NATS server
To keep things simple for this example, we are using an embedded NATS server which is setup in the central server as below:
1 opts := &natsServer.Options{
2 Port: 4222,
3 }
4 ns, err := natsServer.NewServer(opts)
5 if err != nil {
6 log.Fatal(err)
7 }
8 go ns.Start()
Line #2 denotes that the embedded NATS server should run on port 4222.
Line #8 go ns.Start()
The go keyword creates a new goroutine, which is a lightweight thread managed by the Go runtime. Goroutines are a fundamental part of Go’s concurrency model.
When this line executes, ns.Start()
runs concurrently with the rest of the program. The main execution doesn’t wait for this method to complete but continues immediately to the next line. Such pattern is commonly used when launching background services, servers, or long-running processes that need to operate independently of the main program flow.
Publishing NATS message
Let’s look at the agent code
1 func main() {
2 nc, err := nats.Connect("nats://127.0.0.1:4222")
3 if err != nil {
4 log.Fatal(err)
5 }
6 defer nc.Close()
7 log.Println("Connected to NATS server at", nc.ConnectedUrl())
8 for c := time.Tick(1 * time.Second); ; <-c {
9 v, _ := mem.VirtualMemory()
10 cpuPercent, _ := cpu.Percent(0, false)
11 diskUsage, _ := disk.Usage("/")
12
13 stats := &api.BaseStatsReply{
14 Cpu: &cpuPercent[0],
15 Mem: &v.UsedPercent,
16 Disk: &diskUsage.UsedPercent,
17 }
18 msg, err := proto.Marshal(stats)
19 if err != nil {
20 log.Fatal(err)
21 }
22 err = nc.Publish("host.stats.1", msg)
23 if err != nil {
24 log.Fatal(err)
25 }
26 log.Printf("Published message to NATS server, #%v", stats)
27 }
28 }
Line 2 nats.Connect("nats://127.0.0.1:4222")
initializes client connection to the NATS server.
Line 22 nc.Publish("host.stats.1", msg)
publishes a single Protobuf serialized message containing the host base stats viz. CPU utilization, Memory utilization & Disk Usage.
Why
- Learn advanced concepts in Golang with real-life projects
- (Try to) Build a better Golang based open-source alternative to https://uptime.kuma.pet
- To pique my interest in terminal user interface (TUI)