Network diagnostics with Go
No Longer Toy-Sized
Moving on from the stopwatch example, the actual application checks the network in the background and periodically refreshes the results of all tests in the graphical interface.
The program compiled from Listing 4 goes by the name of wifi
, but it can be applied to wired networks in exactly the same way. To display the test results, it uses the tview project's table widget in line 10. A total of five rows, each with two columns, contain a description of the test on the left and the dynamically refreshed result on the right (see Figures 1 and 2).
Listing 4
wifi.go
¤¤nonumber 01 package main 02 03 import ( 04 "strings" 05 "github.com/rivo/tview" 06 ) 07 08 func main() { 09 app := tview.NewApplication() 10 table := tview.NewTable().SetBorders(true) 11 table.SetBorder(true).SetTitle("Wifi Monitor v1.0") 12 13 newPlugin(app, table, "Time", clock) 14 newPlugin(app, table, "Ping", ping, "www.google.com") 15 newPlugin(app, table, "Ping", ping, "8.8.8.8") 16 newPlugin(app, table, "Ifconfig", nifs) 17 newPlugin(app, table, "HTTP", httpGet, "https://youtu.be") 18 19 err := app.SetRoot(table, true).SetFocus(table).Run() 20 if err != nil { 21 panic(err) 22 } 23 } 24 25 func newPlugin(app *tview.Application, table *tview.Table, 26 field string, fu func(...string) chan string, arg ...string) { 27 if len(arg) > 0 { 28 field += " " + strings.Join(arg, " ") 29 } 30 31 row := table.GetRowCount() 32 table.SetCell(row, 0, tview.NewTableCell(field)) 33 34 ch := fu(arg...) 35 36 go func() { 37 for { 38 select { 39 case val := <-ch: 40 app.QueueUpdateDraw(func() { 41 table.SetCell(row, 1, tview.NewTableCell(val)) 42 }) 43 } 44 } 45 }() 46 }
When defining the window and table decorations, you need to look carefully. The table
widget has a SetBorders()
function that determines whether or not the table draws row and column lines. On the other hand, line 11 calls SetBorder()
(singular). SetBorder()
does not refer to the table, but instead to the box (a container) in which the table is located. The call draws a border around the application, along with a headline at the top.
Lumped Together
Each table row is now assigned a test program. The ticking clock ends up in the first row, the two network pings in rows 2 and 3, the display of the local IPs in row 4, and the HTTP request to the YouTube server in row 5. The newPlugin()
function integrates these plugins with the table rows. The calls are each given a pointer to the application and its table in lines 13 to 17. And there are two more parameters: a description of each test as a string and a function that executes the test.
As you can see from the signature of newPlugin()
in line 25, the function expects the test function fu
passed to it to be in an interesting format. To accommodate all applications, the test function accepts a variable number of string arguments (...string
) and returns a channel where the caller can later fetch results of the string
type. Listing 2 has already provided an example of this type of test function: clock()
creates a stopwatch whose current timestamp the table now displays every second in its first row.
To associate the test function with the next available table row, line 32 appends a new row to the table for each call. Then line 34 calls the test function, which in turn returns a channel and keeps its network test running in the background for all eternity. To intercept the results for the individual test, line 36 starts a new concurrent goroutine with an infinite loop that uses a select
statement to listen on the channel. When a string arrives, line 41 uses table.SetCell
to refresh the contents of the assigned table field.
In order for the content of the updated graphical elements to actually appear on the screen, I need to forward the instruction to the GUI manager. This is done by the app.QueueUpdateDraw()
function, which tells the GUI to redraw the table field when it gets around to it during the next refresh.
Ding-Dong
I now need to integrate the new network tests into the table. Each test consists of a function that accepts an optional string argument and returns a channel. It starts the test task assigned to it and keeps it running while returning the results to the caller via the channel.
Listing 5 uses the ping()
function to ping servers or their IP addresses; it expects either a hostname or an IP address as an argument. It returns a channel to the caller, which it keeps populating with ping results.
Listing 5
ping.go
¤¤nonumber 01 package main 02 03 import ( 04 "fmt" 05 "github.com/prometheus-community/pro-bing" 06 "time" 07 ) 08 09 func ping(addr ...string) chan string { 10 ch := make(chan string) 11 firstTime := true 12 13 go func() { 14 for { 15 pinger, err := probing.NewPinger(addr[0]) 16 pinger.Timeout, _ = time.ParseDuration("10s") 17 18 if err != nil { 19 ch <- err.Error() 20 time.Sleep(10 * time.Second) 21 continue 22 } 23 24 if firstTime { 25 ch <- "Pinging ..." 26 firstTime = false 27 } 28 29 pinger.Count = 3 30 err = pinger.Run() 31 32 if err != nil { 33 ch <- err.Error() 34 time.Sleep(10 * time.Second) 35 continue 36 } 37 38 stats := pinger.Statistics() 39 ch <- fmt.Sprintf("%v ", stats.Rtts) 40 time.Sleep(10 * time.Second) 41 } 42 }() 43 return ch 44 }
With a similar interface as the command-line ping
utility, Listing 5 uses the pro-bing package from GitHub to send ICMP packets to the specified address. The package is fetched from GitHub in line 5. The new pinger
instance created in line 15 sets a timeout of 10 seconds in line 16. When the timer for a request expires, the pinger assumes that something went wrong and the server cannot be reached. Another cause for a failure could be a problem with the name resolution for the server. Line 33 will inject an error message into the channel. Line 34 then waits for 10 seconds, and then continue
in the following line jumps to the next iteration of the infinite for
loop starting in line 14 and tries again.
« Previous 1 2 3 Next »
Buy this article as PDF
(incl. VAT)
Buy Linux Magazine
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters
Support Our Work
Linux Magazine content is made possible with support from readers like you. Please consider contributing when you’ve found an article to be beneficial.
News
-
Latest Cinnamon Desktop Releases with a Bold New Look
Just in time for the holidays, the developer of the Cinnamon desktop has shipped a new release to help spice up your eggnog with new features and a new look.
-
Armbian 24.11 Released with Expanded Hardware Support
If you've been waiting for Armbian to support OrangePi 5 Max and Radxa ROCK 5B+, the wait is over.
-
SUSE Renames Several Products for Better Name Recognition
SUSE has been a very powerful player in the European market, but it knows it must branch out to gain serious traction. Will a name change do the trick?
-
ESET Discovers New Linux Malware
WolfsBane is an all-in-one malware that has hit the Linux operating system and includes a dropper, a launcher, and a backdoor.
-
New Linux Kernel Patch Allows Forcing a CPU Mitigation
Even when CPU mitigations can consume precious CPU cycles, it might not be a bad idea to allow users to enable them, even if your machine isn't vulnerable.
-
Red Hat Enterprise Linux 9.5 Released
Notify your friends, loved ones, and colleagues that the latest version of RHEL is available with plenty of enhancements.
-
Linux Sees Massive Performance Increase from a Single Line of Code
With one line of code, Intel was able to increase the performance of the Linux kernel by 4,000 percent.
-
Fedora KDE Approved as an Official Spin
If you prefer the Plasma desktop environment and the Fedora distribution, you're in luck because there's now an official spin that is listed on the same level as the Fedora Workstation edition.
-
New Steam Client Ups the Ante for Linux
The latest release from Steam has some pretty cool tricks up its sleeve.
-
Gnome OS Transitioning Toward a General-Purpose Distro
If you're looking for the perfectly vanilla take on the Gnome desktop, Gnome OS might be for you.