Golang Os/Exec, Realtime Memory Usage

Golang os/exec, realtime memory usage

Here is what I use on Linux:

func calculateMemory(pid int) (uint64, error) {

f, err := os.Open(fmt.Sprintf("/proc/%d/smaps", pid))
if err != nil {
return 0, err
}
defer f.Close()

res := uint64(0)
pfx := []byte("Pss:")
r := bufio.NewScanner(f)
for r.Scan() {
line := r.Bytes()
if bytes.HasPrefix(line, pfx) {
var size uint64
_, err := fmt.Sscanf(string(line[4:]), "%d", &size)
if err != nil {
return 0, err
}
res += size
}
}
if err := r.Err(); err != nil {
return 0, err
}

return res, nil
}

This function returns the PSS (Proportional Set Size) for a given PID, expressed in KB. If you have just started the process, you should have the rights to access the corresponding /proc file.

Tested with kernel 3.0.13.

Measuring memory usage of executable run using golang

You need to do this through the OS itself. If you are on plan9 or posix, Go will return the usage values from the OS for you in the structure returned by ProcessState.SysUsage().

cmd := exec.Command("command", "arg1", "arg2")
err := cmd.Run()
if err != nil {
log.Fatal(err)
}
// check this type assertion to avoid a panic
fmt.Println("MaxRSS:", cmd.ProcessState.SysUsage().(*syscall.Rusage).Maxrss)

Note: different platforms may return this in bytes or kilobytes. Check man getrusage for details.

Is golang channel memory usage dynamic?

pprof can tell you where you spend memory. Simply add an import statement for the net/http/pprof package and start an HTTP server with the http.DefaultServeMux:

import _ "net/http/pprof"

func main() {
go func() { log.Fatal(http.ListenAndServe(":4000", nil)) }()

//...
}

While the program is running, run the pprof tool to look at various statistics about your program. Since you are concerned about memory usage, the heap profile (memory-in-use) is probably most relevant.

$ go tool pprof -top 10 http://localhost:4000/debug/pprof/heap
Fetching profile over HTTP from http://localhost:4000/debug/pprof/heap
File: foo
Build ID: 10
Type: inuse_space
Time: Dec 21, 2018 at 12:52pm (CET)
Showing nodes accounting for 827.57MB, 99.62% of 830.73MB total
Dropped 9 nodes (cum <= 4.15MB)
flat flat% sum% cum cum%
778.56MB 93.72% 93.72% 796.31MB 95.86% time.NewTimer
18.25MB 2.20% 95.92% 18.25MB 2.20% time.Sleep
17.75MB 2.14% 98.05% 17.75MB 2.14% time.startTimer
11MB 1.32% 99.38% 11MB 1.32% runtime.malg
2MB 0.24% 99.62% 798.31MB 96.10% main.(*Session).readLoop
0 0% 99.62% 798.31MB 96.10% main.(*Session).Serve
0 0% 99.62% 18.25MB 2.20% main.(*Session).sendLoop
0 0% 99.62% 800.81MB 96.40% main.Loop
0 0% 99.62% 11.67MB 1.40% runtime.mstart
0 0% 99.62% 11.67MB 1.40% runtime.newproc.func1
0 0% 99.62% 11.67MB 1.40% runtime.newproc1
0 0% 99.62% 11.67MB 1.40% runtime.systemstack
0 0% 99.62% 796.31MB 95.86% time.After

Unsurprisingly, the huge amount of time.Timers you are creating with time.After accounts for pretty much all of the memory in use.

Think about it: With an interval of 250ms you are creating timers 4 times faster than with an interval of 1s. However, the lifetime of the timers is not proportional to the interval -- it is constant at 60 seconds. So at any given point you have 4*60=240 times more timers alive.

From the docs of time.After:

After waits for the duration to elapse and then sends the current time on the returned channel. It is equivalent to NewTimer(d).C. The underlying Timer is not recovered by the garbage collector until the timer fires. If efficiency is a concern, use NewTimer instead and call Timer.Stop if the timer is no longer needed.

So create a single timer per readLoop and re-use it. You can further reduce memory usage by using a channel of empty struct values instead of a channel of booleans:

type Session struct {
KeepAlive chan struct{}
}

func (s *Session) readLoop() {
fmt.Println("readLoop")

d := 1 * time.Minute
t := time.NewTimer(d)

loop:
for {
select {
case _, ok := <-s.KeepAlive:
if !ok {
break loop
}

if !t.Stop() {
<-t.C
}
t.Reset(d)

case <-t.C:
fmt.Println("Timeout")
break loop
}
}

fmt.Println("readLoop EXIT")
}

func (s *Session) sendLoop() {
defer close(s.KeepAlive)

for {
s.KeepAlive <- struct{}{}
time.Sleep(interval)
}
}

Copy exec.Command output to file as the buffer receives data

Why don't you just write to a file directly?

file, _ := os.Create("/some/file")
cmd.Stdout = file

Or use your io thing (that's a terrible name for a variable, by the way, since it's a) the name of a standard library package, b) ambiguous--what does it mean?)

cmd.Stdout = io

Print the stdout from exec command in real time in Go

Thank you mh-cbon. That pushed me in the right direction.

The code now looks like this and does exactly what I want it to do. I also found that when I use Run() instead of Start() the execution of the program only continues after the command has finished.

cmd := exec.Command(command, args...)
cmd.Dir = dir

var stdBuffer bytes.Buffer
mw := io.MultiWriter(os.Stdout, &stdBuffer)

cmd.Stdout = mw
cmd.Stderr = mw

// Execute the command
if err := cmd.Run(); err != nil {
log.Panic(err)
}

log.Println(stdBuffer.String())


Related Topics



Leave a reply



Submit