windowsgogtk3

How can I read Windows memory usage?


I have a data forwarding application running on Windows in Go. This has a gui front end using gotk3/gtk3. Under normal conditions Windows reports the program using about 25MB RAM.

The gui displays channel statistics that update every 2 seconds, I only update these stats when the gui has top-level focus. I might try increasing the update interval for a start.

When the updates are running, memory use slowly but inexorably builds up, until the program crashes after an hour or so, however this seems to be memory outside the Go memstats. Numbers reported by memstats don't change much, so it must be memory that gtk3 uses and Windows doesn't get back.

Once the program loses the focus and the stats update stops, the memory use stops growing but Windows doesn't get it back for another 7 or 8 minutes, when memory usage starts dropping steadily back to where it should be.

I have no idea how to stop the memory creep, my guess is that the gotk3 wrapping for gtk3 isn't releasing memory as fast as it can use it up.

My question is can I monitor what Windows has allocated to the program within Go, then I could suspend stats updates until memory use falls again?

I can't find a package that will give me that information, as I say none of the Go runtime.Memstats seem to match what Windows says.

Code added because a) everyone likes a bit of code and b) someone might know how to stop gotk3 using up memory.

func updateStats() {
    MainListStore.ForEach(func(tmodel *gtk.TreeModel, path *gtk.TreePath, iterfe *gtk.TreeIter) bool {
        // get the channel no
        value, _ := tmodel.GetValue(iterfe, MCOL_INPORT)
        goValue, _ := value.GoValue()
        key := goValue.(int)
        // copy stats to liststore
        Mutex.Lock()
        err := MainListStore.Set(iterfe,
            []int{MCOL_STATPIX, MCOL_STATINT, MCOL_SPDIN, MCOL_SPDOUT},
            []interface{}{Pixes[Statmap[key][0]], Statmap[key][0], Statmap[key][1], Statmap[key][2]})
        Mutex.Unlock()
        if err != nil {
            Logit.Printf("Error: error updating stats chan %d, %v", key, err)
        }
        return false // keep iterating
    })
    return
}

Solution

  • Use Windows API via syscall or golang.org/x/sys

    You can use the syscall or golang.org/x/sys/windows packages to interact with Windows APIs. Specifically, the GlobalMemoryStatusEx or GetProcessMemoryInfo APIs can give you system-level memory usage stats, including private bytes and working set size (This is what Task Manager typically reports).

    I prepared one example using GetProcessMemoryInfo:

    1. go get golang.org/x/sys/windows
    2. using the GetProcessMemoryInfo API to get the memory usage.
    package main
    
    import (
        "fmt"
        "syscall"
        "unsafe"
        "golang.org/x/sys/windows"
    )
    
    var (
        psapi              = windows.NewLazySystemDLL("psapi.dll")
        procGetProcessMemoryInfo = psapi.NewProc("GetProcessMemoryInfo")
    )
    
    type PROCESS_MEMORY_COUNTERS struct {
        cb                         uint32
        PageFaultCount             uint32
        PeakWorkingSetSize         uintptr
        WorkingSetSize             uintptr
        QuotaPeakPagedPoolUsage    uintptr
        QuotaPagedPoolUsage        uintptr
        QuotaPeakNonPagedPoolUsage uintptr
        QuotaNonPagedPoolUsage     uintptr
        PagefileUsage              uintptr
        PeakPagefileUsage          uintptr
    }
    
    func GetProcessMemoryInfo(handle windows.Handle) (PROCESS_MEMORY_COUNTERS, error) {
        var memCounters PROCESS_MEMORY_COUNTERS
        memCounters.cb = uint32(unsafe.Sizeof(memCounters))
        r, _, err := procGetProcessMemoryInfo.Call(
            uintptr(handle),
            uintptr(unsafe.Pointer(&memCounters)),
            uintptr(memCounters.cb),
        )
        if r == 0 {
            return memCounters, err
        }
        return memCounters, nil
    }
    
    func main() {
        // pid:= windows.GetCurrentProcess()  previously
        pid := windows.CurrentProcess() // Get current process handle (new method name)
        memInfo, err := GetProcessMemoryInfo(pid)
        if err != nil {
            fmt.Printf("Error: %v\n", err)
            return
        }
    
        fmt.Printf("Memory Usage:\n")
        fmt.Printf("Working Set Size: %v bytes\n", memInfo.WorkingSetSize) // actual physical memory used by application
        fmt.Printf("Pagefile Usage: %v bytes\n", memInfo.PagefileUsage) // virtual memory
    }
    
    

    This should give you insight into what Windows reports as the memory usage of your Go application, beyond just the Go heap.

    NOTE: Now you can use this to monitor and manage the application pause or play state based on memory usage.

    Now, If the memory usage still continues to rise, even after stopping updates, it's possible there is a memory leak in the way gotk3 is managing memory.

    In that case i would suggest you manually free resources like surfaces, images, or lists, when no longer needed. Also keep in mind that GTK objects need proper cleanup, and Go's garbage collector won't automatically clean up resources allocated outside of Go (like GTK).