golang + sse + Data-Star for real time sys stats
Hello everyone, today I want to show you, how simple with https://data-star.dev hypermedia framework to is to make real time "things" lets start with some code. tools we need today is: https://templ.guide for html templating https://data-star.dev for realtime frontend updates via SSE https://github.com/inhies/go-bytesize to convert bytes to normal representation like Mb Gb https://github.com/shirou/gopsutil to get sys stats and https://github.com/go-chi/chi for routing first, we need to collect sys stats (memory and cpu) so there is our helper function: type Stats struct { MemTotal string MemPercent float64 MemUsed string MemFree string SwapTotal string CpuUser int CpuSystem int CpuIdle int } func collectStats() Stats { v, _ := mem.VirtualMemory() cp, _ := cpu.Times(false) var stats Stats stats.MemUsed = bytesize.New(float64(v.Used)).String() stats.MemTotal = bytesize.New(float64(v.Total)).String() stats.MemFree = bytesize.New(float64(v.Free)).String() stats.MemPercent = v.UsedPercent for _, c := range cp { stats.CpuSystem += int(c.System) stats.CpuUser += int(c.User) stats.CpuIdle += int(c.Idle) } stats.CpuSystem /= len(cp) stats.CpuUser /= len(cp) stats.CpuIdle /= len(cp) return stats } now once we have our stats we could implement our realtime updates. here is some main code for it: r.Get("/live/stats", func(w http.ResponseWriter, r *http.Request) { sse := datastar.NewSSE(w, r) ticker := time.NewTicker(1 * time.Second) defer ticker.Stop() for { select { case

Hello everyone,
today I want to show you, how simple with https://data-star.dev hypermedia framework to is to make real time "things"
lets start with some code.
tools we need today is:
https://templ.guide for html templating
https://data-star.dev for realtime frontend updates via SSE
https://github.com/inhies/go-bytesize to convert bytes to normal representation like Mb Gb
https://github.com/shirou/gopsutil to get sys stats
and https://github.com/go-chi/chi for routing
first, we need to collect sys stats (memory and cpu) so there is our helper function:
type Stats struct {
MemTotal string
MemPercent float64
MemUsed string
MemFree string
SwapTotal string
CpuUser int
CpuSystem int
CpuIdle int
}
func collectStats() Stats {
v, _ := mem.VirtualMemory()
cp, _ := cpu.Times(false)
var stats Stats
stats.MemUsed = bytesize.New(float64(v.Used)).String()
stats.MemTotal = bytesize.New(float64(v.Total)).String()
stats.MemFree = bytesize.New(float64(v.Free)).String()
stats.MemPercent = v.UsedPercent
for _, c := range cp {
stats.CpuSystem += int(c.System)
stats.CpuUser += int(c.User)
stats.CpuIdle += int(c.Idle)
}
stats.CpuSystem /= len(cp)
stats.CpuUser /= len(cp)
stats.CpuIdle /= len(cp)
return stats
}
now once we have our stats we could implement our realtime updates. here is some main code for it:
r.Get("/live/stats", func(w http.ResponseWriter, r *http.Request) {
sse := datastar.NewSSE(w, r)
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
for {
select {
case <-r.Context().Done():
return
case <-ticker.C:
stats := collectStats()
sse.MergeFragmentTempl(Stat(stats))
}
}
})
this collects stats every one second, and publishes it to Data-Star via SSE
and our index page renders everything at initial load:
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
stats := collectStats()
Main(stats).Render(r.Context(), w)
})
Main(statS) is main layout with doctype and all partials like "stats" section. In piece of code for /live/stats - it renders only part of page and sends that part as html to data-star which updates stats element. (not whole page are send)
and here is our templ.guide template:
templ Main(stats Stats) {
Sys Stats
@Stat(stats)
}
templ Stat(stats Stats) {
MemFree { fmt.Sprintf("%s", stats.MemFree) }
MemUsed { fmt.Sprintf("%s",stats.MemUsed) }
MemTotal { fmt.Sprintf("%s",stats.MemTotal) }
MemPercent { fmt.Sprintf("%f",stats.MemPercent) }
CPU system { fmt.Sprintf("%d",stats.CpuSystem) }
CPU user { fmt.Sprintf("%d",stats.CpuUser) }
CPU idle { fmt.Sprintf("%d",stats.CpuIdle) }
}
templ Main - accepts stats as argument, renders whole html and subscribes for changes on /live/stats url.
templ Stat - accepts stats as argument, renders only that part and will be sent to SSE (idiomorph finds element with same id and replaces it )
example could be found at https://github.com/blinkinglight/go-sysstats clone, run go run .
and navigate to https://localhost:8088 and it should just work.