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.Timer
s 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
Cache Coloring on Slab Memory Management in Linux Kernel
How to Use Ptrace(2) to Change Behaviour of Syscalls
How to Install Xclip on an Ec2 Instance
Simulate Effect of Select() and Poll() in Kernel Socket Programming
Amazon Linux Ami Ec2 Gui/Remote Desktop
How to Log Messages from an ASP.NET Core Application to a Specific File on Linux
What's The Difference Between /Usr/Include/Linux and The Include Folder in Linux Kernel Source
What Is The Safest Way to Empty a Directory in *Nix
Dos2Unix: Binary Symbol Found, Skipping Binary File
Linux Async (Io_Submit) Write V/S Normal (Buffered) Write
Would It Be Possible to Read Out Physical Keyboard Strokes in Node.Js
Make Bash Differentiate Between Ctrl-<Letter> and Ctrl-Shift-<Letter>
Unattended Install of Krb5-User on Ubuntu 16.04
Docker Create Two Bridges That Corrupts My Internet Access