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) {
<!DOCTYPE html>
<html>
<head>
<title>Sys Stats</title>
<script type="module" src="https://cdn.jsdelivr.net/gh/starfederation/datastar@v1.0.0-beta.9/bundles/datastar.js"></script>
</head>
<body data-on-load="@get('/live/stats')"></body>
@Stat(stats)
</html>
}
templ Stat(stats Stats) {
<div id="stats">
<p><strong>MemFree</strong> { fmt.Sprintf("%s", stats.MemFree) }</p>
<p><strong>MemUsed</strong> { fmt.Sprintf("%s",stats.MemUsed) }</p>
<p><strong>MemTotal</strong> { fmt.Sprintf("%s",stats.MemTotal) }</p>
<p><strong>MemPercent</strong> { fmt.Sprintf("%f",stats.MemPercent) }</p>
<p><strong>CPU system</strong> { fmt.Sprintf("%d",stats.CpuSystem) }</p>
<p><strong>CPU user</strong> { fmt.Sprintf("%d",stats.CpuUser) }</p>
<p><strong>CPU idle</strong> { fmt.Sprintf("%d",stats.CpuIdle) }</p>
</div>
}
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.
Top comments (0)