Monitor hard disk usage with Go
Programming Snapshot – Disk Speedo
To keep an eye on the remaining disk space during storage-intensive operations, you can check out this speedometer/odometer written in Go.
Storage-hungry applications such as video-editing software gobble up disk capacity like a massive vacuum cleaner: Before you know it, everything has been used up. In situations such as this, incorrectly programmed software tends to crash, and all the time you invested in your current project is irretrievably lost. If you take precautions up front, you can avoid headaches later on.
How about a constantly updated display of the remaining space on a dashboard-like instrument on the desktop, where you can see out of the corner of your eye how much disk space is wasted by an action that has just been triggered, such as rendering a video? You can write something like this quickly in Go.
On the Dashboard
A car's dashboard shows the current speed as well as the mileage. If you apply this to hard disks, the car's mileage reading becomes the total disk space consumed. Similarly, where in a car the speedometer needle shows the current speed, in the hard disk universe, the needle measures the space consumed per unit of time. The analogy for a storage-hungry application would be a speeding traffic offender, if you like.
Figures 1 and 2 show the terminal output from the finished Go program, Disk Speedo (or dftop
). At the top, a progress bar illustrates the amount of disk space used thus far (in this case, 35 percent). At the bottom, a speedometer needle implemented as a pie chart indicates whether space is disappearing (red) or coming back (green) and how fast this is happening. At a simulated speed of 0 to 100, the needle of this somewhat unusual instrument starts at the bottom of the circle and then moves upwards in a counter-clockwise direction. Figure 1 shows a write speed of 35; Figure 2 shows a delete action at a speed of 65. At a speed of 100, the circle would be completely filled with the corresponding color, red or green.
Avoid Shell Calls
To determine the remaining space on a data volume, the program could repeatedly call the df
shell function. But this would waste valuable resources, because the shell would have to start a new df
process each time. Fortunately, the nifty statfs
programming interface [1] on Unix systems reports the total number of blocks provided on the corresponding mount as well as the blocks on the storage medium that are still unoccupied, without needing to call any shell utilities.
The Go interface for statfs
gives you the total number of free blocks with Bfree()
and the subset of free blocks that the non-root user can occupy with Bavail()
. Multiplying these numbers by the block size defined on the storage medium as Bsize()
gives you the remaining storage space in tera-, giga-, or whatever-bytes.
The space()
function from line 61 in Listing 1 [2] determines the utilization of the storage medium and returns values for the number of occupied blocks, as well as their total number on a particular volume. If an error occurs when determining the capacity, space()
pass it back as the third return value to the calling main program. But which hard disk's capacity will the program actually measure on a system with multiple storage media? Depending on the directory from which you call the speedometer, it will display the space on the hard disk it is residing on.
Listing 1
dftop.go
01 package main 02 import ( 03 "container/ring" 04 "golang.org/x/sys/unix" 05 "os" 06 "time" 07 ) 08 09 func main() { 10 wd, err := os.Getwd() 11 if err != nil { 12 panic(err) 13 } 14 15 ui := NewUI() 16 ui.Update(0, 0.0) 17 uidone := ui.Run() 18 defer ui.Close() 19 r := ring.New(2) 20 21 for { 22 used, total, err := space(wd) 23 if err != nil { 24 panic(err) 25 } 26 27 r.Value = used 28 p := used * 100 / total 29 ui.Update(int(p), speed(r)) 30 r = r.Next() 31 32 select { 33 case <-uidone: 34 return 35 case <-time.After( 36 1 * time.Second): 37 continue 38 } 39 } 40 } 41 42 const maxSpeed = 100000 43 44 func speed(r *ring.Ring) float64 { 45 if r.Prev().Value == nil { 46 return 0 47 } 48 s := float64(int( 49 r.Value.(uint64)- 50 r.Prev().Value.(uint64))) / 51 maxSpeed 52 53 if s > 1 { 54 s = 1 55 } else if s < -1 { 56 s = -1 57 } 58 return s 59 } 60 61 func space(dir string) ( 62 uint64, uint64, error) { 63 var stat unix.Statfs_t 64 err := unix.Statfs(dir, &stat) 65 return stat.Blocks - 66 stat.Bfree, stat.Blocks, err 67 }
Storage in a Circle
The speed at which the hard disk fills up is defined by the difference between two measurements of the fill level at different times divided by the time elapsed between them.
To do this, the program needs to store one or more past measurements in order to determine the delta to the currently measured value. This could be implemented using separate variables, but a ring buffer (Figure 3) of the container/ring
type from the Go standard library does this in an elegant way without much code.
The ring buffer stores new values in r.Value
, in sequence, in points that lie on a circular path. r.Next()
moves to the next point, and r.Prev()
goes back to the previous one. If the algorithm arrives at the first point again at some point on its circular path, it simply overwrites it. A ring buffer can only ever access the N most recent values, but it does not clutter the system's memory with irrelevant values from the past. A ring buffer created as shown in line 19 (Listing 1) with only two entries certainly does not leverage the data structure's full potential, but if desired, you can expand the buffer to include more entries for averaging and smoothing the display.
The speed()
function starting in line 44 computes the current filling speed of the storage medium using this procedure. If the ring buffer does not yet carry two values because the algorithm just started out, the speed cannot yet be determined and line 46 returns the value
.
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.