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) }
    
}




    Enter fullscreen mode
    


    Exit fullscreen mode
    




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.