Automating Internet Speed Tests with Go: A Practical Project for Developers
Monitoring your internet speed over time is useful for developers, network engineers, and anyone who wants to verify ISP consistency or diagnose connectivity issues. In this post, we'll build a Go program that automates internet speed testing using the speedtest-go library. It logs results in a CSV file every 10 seconds and prints real-time results to the terminal.
Why Build a Speed Test Tool?
There are many browser-based tools for checking internet speed, but few offer automation, logging, and programmatic control. This project is ideal if you want to:
Collect long-term data on download/upload speeds and latency
Monitor your home or office network
Create alerts for slowdowns
Run tests on edge devices like Raspberry Pi
Go's efficiency, simplicity, and concurrency make it a perfect choice for this task.
What We'll Build
Our application:
✅ Runs a speed test every 10 seconds
✅ Logs results in a CSV file
✅ Prints metrics to the console
✅ Handles graceful shutdown with Ctrl+C
The final CSV file includes:
Timestamp, Download (Mbps), Upload (Mbps), Latency (ms), Server Name, Server Location
Required Dependencies
You'll need the speedtest-go library for accessing Speedtest.net servers.
sh
go get github.com/showwin/speedtest-go/speedtest
Project Structure
Here's how we'll organize the files:
/speedtest-monitor
├── main.go
└── speed/
└── performance.go
This modular structure separates concerns and makes the code easier to maintain.
main.go — The Core Controller
This file handles setup, scheduling, and safe shutdown:
go
package main
import (
"encoding/csv"
"fmt"
"log"
"os"
"os/signal"
"syscall"
"time"
"github.com/showwin/speedtest-go/speedtest"
"speed/speed"
)
func main() {
log.SetFlags(log.LstdFlags | log.Lshortfile)
file, err := os.OpenFile("speedtest_results.csv", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatalf("Failed to open file: %v", err)
}
defer file.Close()
writer := csv.NewWriter(file)
defer writer.Flush()
fileInfo, err := file.Stat()
if err != nil {
log.Fatalf("Failed to get file info: %v", err)
}
if fileInfo.Size() == 0 {
headers := []string{"Timestamp", "Download (Mbps)", "Upload (Mbps)", "Latency (ms)", "Server Name", "Server Location"}
if err := writer.Write(headers); err != nil {
log.Fatalf("Failed to write headers: %v", err)
}
}
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
_, err = speedtest.FetchUserInfo()
if err != nil {
log.Fatalf("Error fetching user info: %v", err)
}
serverList, err := speedtest.FetchServers()
if err != nil {
log.Fatalf("Error fetching servers: %v", err)
}
ticker := time.NewTicker(10 * time.Second)
defer ticker.Stop()
fmt.Println("Running speed tests every 10 seconds. Press Ctrl+C to stop.")
fmt.Println("Results will be saved to speedtest_results.csv")
fmt.Println("--------------------------------------------------")
speed.PerformTest(serverList, writer)
for {
select {
case <-ticker.C:
speed.PerformTest(serverList, writer)
case <-signalChan:
fmt.Println("\nGracefully shutting down...")
fmt.Println("Final results saved to speedtest_results.csv")
return
}
}
}
speed/performance.go — The Test Logic
This module performs the download, upload, and latency tests:
go
package speed
import (
"encoding/csv"
"fmt"
"log"
"time"
"github.com/showwin/speedtest-go/speedtest"
)
func PerformTest(serverList speedtest.Servers, writer *csv.Writer) {
targets, err := serverList.FindServer([]int{})
if err != nil || len(targets) == 0 {
log.Printf("Error finding server: %v\n", err)
return
}
bestServer := targets[0]
err = bestServer.DownloadTest()
if err != nil {
log.Printf("Download test failed: %v\n", err)
return
}
err = bestServer.UploadTest()
if err != nil {
log.Printf("Upload test failed: %v\n", err)
return
}
downloadSpeed := bestServer.DLSpeed / 1000000
uploadSpeed := bestServer.ULSpeed / 1000000
latency := float64(bestServer.Latency.Milliseconds())
serverName := bestServer.Name
serverLocation := bestServer.Country
timestamp := time.Now().Format("2006-01-02 15:04:05")
record := []string{
timestamp,
fmt.Sprintf("%.2f", downloadSpeed),
fmt.Sprintf("%.2f", uploadSpeed),
fmt.Sprintf("%.2f", latency),
serverName,
serverLocation,
}
if err := writer.Write(record); err != nil {
log.Printf("Error writing to CSV: %v\n", err)
}
writer.Flush()
fmt.Printf("\n[%s]\n", timestamp)
fmt.Printf("Server: %s (%s)\n", serverName, serverLocation)
fmt.Printf("Download: %.2f Mbps\n", downloadSpeed)
fmt.Printf("Upload: %.2f Mbps\n", uploadSpeed)
fmt.Printf("Latency: %.2f ms\n", latency)
fmt.Println("--------------------------------------------------")
}
Sample Output
Here's what the console output looks like:
[2025-05-24 12:45:00]
Server: Telecom XYZ (Germany)
Download: 85.62 Mbps
Upload: 23.47 Mbps
Latency: 14.30 ms
And the CSV output:
Timestamp,Download (Mbps),Upload (Mbps),Latency (ms),Server Name,Server Location
2025-05-24 12:45:00,85.62,23.47,14.30,Telecom XYZ,Germany
Graceful Shutdown
Using Go's os/signal package, the app listens for Ctrl+C and safely closes the file, ensuring all results are saved before exiting.
Potential Enhancements
Export to a time-series database like InfluxDB or Prometheus
Create visual dashboards using Grafana
Send alerts via email or Slack on bandwidth drop
Schedule tests with cron instead of a ticker
Conclusion
This project is a practical example of how Go's simplicity and performance make it ideal for automation tasks. You now have a working tool to log and analyze internet speed data programmatically. Feel free to expand it, containerize it, or hook it into your home lab.
Happy coding!
🔗
Top comments (0)