-
-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathmemplot.go
159 lines (136 loc) · 3.48 KB
/
memplot.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
package memplot
import (
"errors"
"fmt"
"github.com/shirou/gopsutil/process"
"gonum.org/v1/plot"
"gonum.org/v1/plot/plotter"
"gonum.org/v1/plot/vg"
"image/color"
"time"
)
// Process data for a given instant
type Instant struct {
MemoryInfo *process.MemoryInfoStat
NumThreads int32
Instant time.Duration
}
type Collection struct {
Pid int32
StartTime time.Time
SampleDuration time.Duration // Time between samples
Samples []Instant
}
// Gather a process resident size in memory in batch
func NewCollection(pid int32, sd, dur time.Duration) (*Collection, error) {
numsamples := dur / sd
if dur != 0 && numsamples < 2 {
return nil, errors.New("There must be at least two samples. Sample Duration too short")
}
proc, err := process.NewProcess(pid)
if err != nil {
return nil, err
}
start := time.Now()
var mem *process.MemoryInfoStat
var nthreads int32
coll := &Collection{
Pid: pid,
StartTime: start,
SampleDuration: sd,
Samples: make([]Instant, 0),
}
// el = elapsed time, dur = total duration
running, err := proc.IsRunning()
if err != nil {
return nil, err
}
for el := time.Since(start); (dur == 0 || el <= dur) && running; el = time.Since(start) {
mem, err = proc.MemoryInfo()
if err != nil {
return nil, err
}
nthreads, err = proc.NumThreads()
if err != nil {
return nil, err
}
instant := Instant{
MemoryInfo: mem,
Instant: el,
NumThreads: nthreads,
}
coll.Samples = append(coll.Samples, instant)
time.Sleep(sd)
running, err = proc.IsRunning()
if err != nil {
return nil, err
}
}
return coll, nil
}
// Gather RSS points from a memory collection
func (m *Collection) GatherRSSXYs() plotter.XYs {
pts := make(plotter.XYs, len(m.Samples))
for i, s := range m.Samples {
pts[i].X = s.Instant.Seconds()
pts[i].Y = float64(m.Samples[i].MemoryInfo.RSS) / 1024
}
return pts
}
// Gather VSZ points from a memory collection
func (m *Collection) GatherVSZXYs() plotter.XYs {
pts := make(plotter.XYs, len(m.Samples))
for i, s := range m.Samples {
pts[i].X = s.Instant.Seconds()
pts[i].Y = float64(m.Samples[i].MemoryInfo.VMS) / 1024
}
return pts
}
type PlotOptions struct {
PlotRss bool
PlotVsz bool
// PlotNumThreads bool
}
// Plot a memory collection
func (m *Collection) Plot(opt PlotOptions) (*plot.Plot, error) {
p, err := plot.New()
if err != nil {
return nil, err
}
p.Title.Text = fmt.Sprintf("Memory Plot of PID %d", m.Pid)
p.X.Label.Text = "Time (Seconds)"
p.Y.Label.Text = "KiloBytes"
// Draw a grid behind the area
p.Add(plotter.NewGrid())
if opt.PlotRss {
// RSS line plotter and style
rssData := m.GatherRSSXYs()
rssLine, err := plotter.NewLine(rssData)
if err != nil {
return nil, err
}
rssLine.LineStyle.Width = vg.Points(1)
rssLine.LineStyle.Color = color.RGBA{R: 0, G: 0, B: 0, A: 255}
// Add the plotters to the plot, with legend entries
p.Add(rssLine)
p.Legend.Add("RSS", rssLine)
}
// TODO add another Y axis for vsz
if opt.PlotVsz {
// RSS line plotter and style
vszData := m.GatherVSZXYs()
vszLine, err := plotter.NewLine(vszData)
if err != nil {
return nil, err
}
vszLine.LineStyle.Width = vg.Points(1)
vszLine.LineStyle.Color = color.RGBA{R: 0, G: 0, B: 255, A: 255}
// Add the plotters to the plot, with legend entries
p.Add(vszLine)
p.Legend.Add("VSZ", vszLine)
}
return p, nil
}
func SavePlot(p *plot.Plot, width, height vg.Length, filename string) error {
return p.Save(width, height, filename)
}