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

Mar 24, 2025 - 08:36
 0
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.

SysStats using golang and data-star.dev

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.