perfmonger 0.10.1 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.gitignore +3 -0
- data/.travis.yml +9 -8
- data/HOWTO.md +0 -1
- data/NEWS +47 -3
- data/README.md +77 -27
- data/Rakefile +20 -9
- data/core/Makefile +10 -18
- data/core/build.sh +2 -2
- data/core/perfmonger-player.go +90 -40
- data/core/perfmonger-plot-formatter.go +18 -4
- data/core/perfmonger-recorder.go +22 -2
- data/core/perfmonger-summarizer.go +20 -14
- data/core/perfmonger-viewer.go +164 -0
- data/core/subsystem/Makefile +4 -0
- data/core/subsystem/perfmonger_linux.go +95 -0
- data/core/subsystem/perfmonger_linux_test.go +40 -0
- data/core/subsystem/stat.go +70 -0
- data/core/subsystem/stat_test.go +9 -0
- data/core/subsystem/usage.go +223 -66
- data/core/subsystem/usage_test.go +62 -32
- data/{bin → exe}/perfmonger +0 -0
- data/lib/perfmonger/command/fingerprint.rb +26 -1
- data/lib/perfmonger/command/live.rb +19 -0
- data/lib/perfmonger/command/play.rb +16 -0
- data/lib/perfmonger/command/plot.rb +25 -9
- data/lib/perfmonger/command/record.rb +1 -1
- data/lib/perfmonger/command/record_option.rb +16 -0
- data/lib/perfmonger/command/server.rb +1 -1
- data/lib/perfmonger/version.rb +1 -1
- data/misc/werker-box/Dockerfile +34 -0
- data/misc/werker-box/build-push.sh +7 -0
- data/perfmonger.gemspec +2 -1
- data/spec/data/busy100.pgr.played +3 -3
- data/spec/fingerprint_spec.rb +1 -1
- data/spec/live_spec.rb +2 -3
- data/spec/perfmonger_spec.rb +1 -1
- data/spec/play_spec.rb +1 -1
- data/spec/plot_spec.rb +16 -1
- data/spec/record_spec.rb +10 -1
- data/spec/spec_helper.rb +28 -3
- data/spec/stat_spec.rb +2 -2
- data/spec/summary_spec.rb +1 -1
- data/wercker.yml +29 -16
- metadata +28 -10
@@ -11,23 +11,30 @@ import (
|
|
11
11
|
"io"
|
12
12
|
"io/ioutil"
|
13
13
|
"os"
|
14
|
+
"regexp"
|
14
15
|
"sort"
|
15
16
|
|
16
17
|
ss "github.com/hayamiz/perfmonger/core/subsystem"
|
17
18
|
)
|
18
19
|
|
19
20
|
type CmdOption struct {
|
20
|
-
DiskFile
|
21
|
-
CpuFile
|
22
|
-
PerfmongerFile
|
21
|
+
DiskFile string
|
22
|
+
CpuFile string
|
23
|
+
PerfmongerFile string
|
24
|
+
disk_only string
|
25
|
+
disk_only_regex *regexp.Regexp
|
23
26
|
}
|
24
27
|
|
25
28
|
func parseArgs() *CmdOption {
|
29
|
+
var err error
|
30
|
+
|
26
31
|
opt := new(CmdOption)
|
27
32
|
|
28
33
|
flag.StringVar(&opt.DiskFile, "diskfile", "./disk.dat", "Disk performance data file")
|
29
34
|
flag.StringVar(&opt.CpuFile, "cpufile", "./cpu.dat", "CPU performance data file")
|
30
35
|
flag.StringVar(&opt.PerfmongerFile, "perfmonger", "", "Perfmonger log file")
|
36
|
+
flag.StringVar(&opt.disk_only, "disk-only",
|
37
|
+
"", "Select disk devices by regex")
|
31
38
|
|
32
39
|
flag.Parse()
|
33
40
|
|
@@ -36,6 +43,11 @@ func parseArgs() *CmdOption {
|
|
36
43
|
os.Exit(1)
|
37
44
|
}
|
38
45
|
|
46
|
+
opt.disk_only_regex, err = regexp.Compile(opt.disk_only)
|
47
|
+
if err != nil {
|
48
|
+
panic(err)
|
49
|
+
}
|
50
|
+
|
39
51
|
return opt
|
40
52
|
}
|
41
53
|
|
@@ -192,7 +204,9 @@ func main() {
|
|
192
204
|
}
|
193
205
|
|
194
206
|
// Disk usage
|
195
|
-
dusage, err := ss.
|
207
|
+
dusage, err := ss.GetDiskUsage1(prev_rec.Time, prev_rec.Disk,
|
208
|
+
cur_rec.Time, cur_rec.Disk,
|
209
|
+
opt.disk_only_regex)
|
196
210
|
if err != nil {
|
197
211
|
panic(err)
|
198
212
|
}
|
data/core/perfmonger-recorder.go
CHANGED
@@ -36,6 +36,7 @@ type RecorderOption struct {
|
|
36
36
|
no_intr bool
|
37
37
|
no_disk bool
|
38
38
|
no_net bool
|
39
|
+
no_mem bool
|
39
40
|
debug bool
|
40
41
|
listDevices bool
|
41
42
|
player_bin string
|
@@ -43,6 +44,8 @@ type RecorderOption struct {
|
|
43
44
|
targetDisks *map[string]bool
|
44
45
|
background bool
|
45
46
|
gzip bool
|
47
|
+
color bool
|
48
|
+
pretty bool
|
46
49
|
}
|
47
50
|
|
48
51
|
var option RecorderOption
|
@@ -74,6 +77,8 @@ func parseArgs() {
|
|
74
77
|
false, "Do not record disk usage")
|
75
78
|
flag.BoolVar(&option.no_net, "no-net",
|
76
79
|
false, "Do not record net usage")
|
80
|
+
flag.BoolVar(&option.no_mem, "no-mem",
|
81
|
+
false, "Do not record memory usage")
|
77
82
|
flag.BoolVar(&option.debug, "debug",
|
78
83
|
false, "Enable debug mode")
|
79
84
|
flag.BoolVar(&option.listDevices, "list-devices",
|
@@ -86,6 +91,10 @@ func parseArgs() {
|
|
86
91
|
"", "Run perfmonger-player to show JSON output")
|
87
92
|
flag.BoolVar(&option.gzip, "gzip",
|
88
93
|
false, "Save a logfile in gzipped format")
|
94
|
+
flag.BoolVar(&option.color, "color",
|
95
|
+
false, "Colored output (for live subcmd)")
|
96
|
+
flag.BoolVar(&option.pretty, "pretty",
|
97
|
+
false, "Pretty output (for live subcmd)")
|
89
98
|
|
90
99
|
flag.Parse()
|
91
100
|
|
@@ -190,7 +199,7 @@ func main() {
|
|
190
199
|
parseArgs()
|
191
200
|
|
192
201
|
hostname, _ := os.Hostname()
|
193
|
-
cheader := &ss.CommonHeader{ss.Linux, hostname, time.Now()}
|
202
|
+
cheader := &ss.CommonHeader{Platform: ss.Linux, Hostname: hostname, StartTime: time.Now()}
|
194
203
|
|
195
204
|
platform_header := ss.NewPlatformHeader()
|
196
205
|
|
@@ -206,7 +215,15 @@ func main() {
|
|
206
215
|
var player_stdout io.ReadCloser = nil
|
207
216
|
|
208
217
|
if option.player_bin != "" {
|
209
|
-
|
218
|
+
if option.color {
|
219
|
+
if option.pretty {
|
220
|
+
player_cmd = exec.Command(option.player_bin, "-color", "-pretty")
|
221
|
+
} else {
|
222
|
+
player_cmd = exec.Command(option.player_bin, "-color")
|
223
|
+
}
|
224
|
+
} else {
|
225
|
+
player_cmd = exec.Command(option.player_bin)
|
226
|
+
}
|
210
227
|
player_stdin, err = player_cmd.StdinPipe()
|
211
228
|
if err != nil {
|
212
229
|
fmt.Fprintf(os.Stderr, "Failed to get stdin of %s", option.player_bin)
|
@@ -326,6 +343,9 @@ func main() {
|
|
326
343
|
if !option.no_net {
|
327
344
|
ss.ReadNetStat(record)
|
328
345
|
}
|
346
|
+
if !option.no_mem {
|
347
|
+
ss.ReadMemStat(record)
|
348
|
+
}
|
329
349
|
|
330
350
|
err = enc.Encode(record)
|
331
351
|
if err != nil {
|
@@ -3,7 +3,6 @@
|
|
3
3
|
package main
|
4
4
|
|
5
5
|
import (
|
6
|
-
"bytes"
|
7
6
|
"encoding/gob"
|
8
7
|
"flag"
|
9
8
|
"fmt"
|
@@ -12,6 +11,7 @@ import (
|
|
12
11
|
"regexp"
|
13
12
|
"sort"
|
14
13
|
|
14
|
+
projson "github.com/hayamiz/go-projson"
|
15
15
|
ss "github.com/hayamiz/perfmonger/core/subsystem"
|
16
16
|
)
|
17
17
|
|
@@ -128,7 +128,7 @@ func main() {
|
|
128
128
|
option.disk_only_regex)
|
129
129
|
}
|
130
130
|
|
131
|
-
if fst_record.
|
131
|
+
if fst_record.Net != nil && lst_record.Net != nil {
|
132
132
|
net_usage, err = ss.GetNetUsage(
|
133
133
|
fst_record.Time, fst_record.Net,
|
134
134
|
lst_record.Time, lst_record.Net)
|
@@ -137,32 +137,38 @@ func main() {
|
|
137
137
|
interval := lst_record.Time.Sub(fst_record.Time)
|
138
138
|
|
139
139
|
if option.json {
|
140
|
-
|
140
|
+
printer := projson.NewPrinter()
|
141
141
|
|
142
|
-
|
142
|
+
printer.BeginObject()
|
143
|
+
printer.PutKey("exectime")
|
144
|
+
printer.PutFloatFmt(interval.Seconds(), "%.3f")
|
143
145
|
if cpu_usage != nil {
|
144
|
-
|
145
|
-
cpu_usage.WriteJsonTo(
|
146
|
+
printer.PutKey("cpu")
|
147
|
+
cpu_usage.WriteJsonTo(printer)
|
146
148
|
}
|
147
149
|
|
148
150
|
if intr_usage != nil {
|
149
|
-
|
150
|
-
intr_usage.WriteJsonTo(
|
151
|
+
printer.PutKey("intr")
|
152
|
+
intr_usage.WriteJsonTo(printer)
|
151
153
|
}
|
152
154
|
|
153
155
|
if disk_usage != nil {
|
154
|
-
|
155
|
-
disk_usage.WriteJsonTo(
|
156
|
+
printer.PutKey("disk")
|
157
|
+
disk_usage.WriteJsonTo(printer)
|
156
158
|
}
|
157
159
|
|
158
160
|
if net_usage != nil {
|
159
|
-
|
160
|
-
net_usage.WriteJsonTo(
|
161
|
+
printer.PutKey("net")
|
162
|
+
net_usage.WriteJsonTo(printer)
|
161
163
|
}
|
162
164
|
|
163
|
-
|
165
|
+
printer.FinishObject()
|
164
166
|
|
165
|
-
|
167
|
+
if str, err := printer.String(); err != nil {
|
168
|
+
fmt.Println("skip by err")
|
169
|
+
} else {
|
170
|
+
fmt.Println(str)
|
171
|
+
}
|
166
172
|
} else {
|
167
173
|
if option.title == "" {
|
168
174
|
fmt.Println("== performance summary ==")
|
@@ -0,0 +1,164 @@
|
|
1
|
+
//usr/bin/env go run $0 $@ ; exit
|
2
|
+
|
3
|
+
package main
|
4
|
+
|
5
|
+
import (
|
6
|
+
"fmt"
|
7
|
+
"log"
|
8
|
+
"math"
|
9
|
+
"math/rand"
|
10
|
+
"time"
|
11
|
+
|
12
|
+
gocui "github.com/jroimartin/gocui"
|
13
|
+
termbox "github.com/nsf/termbox-go"
|
14
|
+
)
|
15
|
+
|
16
|
+
func main() {
|
17
|
+
g, err := gocui.NewGui(gocui.OutputNormal)
|
18
|
+
if err != nil {
|
19
|
+
log.Panicln(err)
|
20
|
+
}
|
21
|
+
defer g.Close()
|
22
|
+
|
23
|
+
g.SetManagerFunc(layout)
|
24
|
+
|
25
|
+
setupKeybind(g)
|
26
|
+
|
27
|
+
go func() {
|
28
|
+
for {
|
29
|
+
time.Sleep(1 * time.Second)
|
30
|
+
g.Update(layout)
|
31
|
+
}
|
32
|
+
}()
|
33
|
+
|
34
|
+
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
|
35
|
+
log.Panicln(err)
|
36
|
+
}
|
37
|
+
}
|
38
|
+
|
39
|
+
func setupKeybind(g *gocui.Gui) {
|
40
|
+
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
41
|
+
log.Panicln(err)
|
42
|
+
}
|
43
|
+
|
44
|
+
if err := g.SetKeybinding("", 'c', gocui.ModNone, changeColor); err != nil {
|
45
|
+
log.Panicln(err)
|
46
|
+
}
|
47
|
+
|
48
|
+
if err := g.SetKeybinding("", 'q', gocui.ModNone, quit); err != nil {
|
49
|
+
log.Panicln(err)
|
50
|
+
}
|
51
|
+
}
|
52
|
+
|
53
|
+
var colmode bool = false
|
54
|
+
|
55
|
+
func changeColor(g *gocui.Gui, v *gocui.View) error {
|
56
|
+
if colmode {
|
57
|
+
colmode = false
|
58
|
+
} else {
|
59
|
+
colmode = true
|
60
|
+
}
|
61
|
+
return nil
|
62
|
+
}
|
63
|
+
|
64
|
+
// Vertical bar characters
|
65
|
+
// ' ': \u0020
|
66
|
+
// '▁': \u2581
|
67
|
+
// '▂': \u2582
|
68
|
+
// '▃': \u2583
|
69
|
+
// '▄': \u2584
|
70
|
+
// '▅': \u2585
|
71
|
+
// '▆': \u2586
|
72
|
+
// '▇': \u2587
|
73
|
+
// '█': \u2588
|
74
|
+
var vbar_runes []rune = []rune(" ▁▂▃▄▅▆▇█")
|
75
|
+
|
76
|
+
func drawPercent(g *gocui.Gui, x int, y int, height int, pct float64, fg, bg termbox.Attribute) {
|
77
|
+
pct = math.Max(0.0, math.Min(100.0, pct))
|
78
|
+
pct_ndiv := 9
|
79
|
+
if height > 1 {
|
80
|
+
pct_ndiv += 8 * (height - 1)
|
81
|
+
}
|
82
|
+
pct_class := int(pct / (100.0 / float64(pct_ndiv)))
|
83
|
+
|
84
|
+
col := termbox.ColorBlue
|
85
|
+
if 25 <= pct && pct < 50 {
|
86
|
+
col = termbox.ColorGreen
|
87
|
+
} else if 50 <= pct && pct < 75 {
|
88
|
+
col = termbox.ColorYellow
|
89
|
+
} else if 75 <= pct {
|
90
|
+
col = termbox.ColorRed
|
91
|
+
}
|
92
|
+
|
93
|
+
if !colmode {
|
94
|
+
col = termbox.ColorWhite
|
95
|
+
}
|
96
|
+
|
97
|
+
for cell_pos := height - 1; cell_pos >= 0; cell_pos-- {
|
98
|
+
vbar_idx := intMin(pct_class, len(vbar_runes)-1)
|
99
|
+
termbox.SetCell(x, y+cell_pos, vbar_runes[vbar_idx], col, bg)
|
100
|
+
pct_class -= vbar_idx
|
101
|
+
}
|
102
|
+
|
103
|
+
// termbox.SetCell(x, y, vbar_runes[pct_class], fg, bg)
|
104
|
+
}
|
105
|
+
|
106
|
+
func drawRunes(g *gocui.Gui, x int, y int, runes []rune) {
|
107
|
+
for _, r := range runes {
|
108
|
+
termbox.SetCell(x, y, r, termbox.ColorDefault, termbox.ColorDefault)
|
109
|
+
x += 1
|
110
|
+
}
|
111
|
+
}
|
112
|
+
|
113
|
+
func intMin(x, y int) int {
|
114
|
+
if x > y {
|
115
|
+
return y
|
116
|
+
} else {
|
117
|
+
return x
|
118
|
+
}
|
119
|
+
}
|
120
|
+
|
121
|
+
var start_time int64 = time.Now().Unix()
|
122
|
+
|
123
|
+
func layout(g *gocui.Gui) error {
|
124
|
+
// maxX, maxY := g.Size()
|
125
|
+
// if v, err := g.SetView("hello", maxX/2-7, maxY/2, maxX/2+7, maxY/2+2); err != nil {
|
126
|
+
// if err != gocui.ErrUnknownView {
|
127
|
+
// return err
|
128
|
+
// }
|
129
|
+
// fmt.Fprintln(v, "Hello world!")
|
130
|
+
// }
|
131
|
+
|
132
|
+
for coreid := 0; coreid < 8; coreid++ {
|
133
|
+
s := int64(coreid + 1)
|
134
|
+
rand.Seed(s)
|
135
|
+
|
136
|
+
inc := int(time.Now().Unix() - start_time)
|
137
|
+
for i := 0; i < inc; i++ {
|
138
|
+
rand.Float64()
|
139
|
+
}
|
140
|
+
|
141
|
+
for i := 0; i < 100; i++ {
|
142
|
+
// drawPercent(g, i+5, 1+coreid*5, 4, float64(i)/2.0, termbox.ColorDefault, termbox.ColorDefault)
|
143
|
+
drawPercent(g, i+6, 1+coreid*5, 4, rand.Float64()*100, termbox.ColorDefault, termbox.ColorDefault)
|
144
|
+
|
145
|
+
ch := '-'
|
146
|
+
if (i+inc)%10 == 0 {
|
147
|
+
ch = '+'
|
148
|
+
}
|
149
|
+
termbox.SetCell(i+6, 1+coreid*5+4, ch, termbox.ColorDefault, termbox.ColorDefault)
|
150
|
+
}
|
151
|
+
drawRunes(g, 0, 1+coreid*5, []rune(fmt.Sprintf("core%d", coreid)))
|
152
|
+
|
153
|
+
drawRunes(g, 100+6+1, 1+coreid*5, []rune(fmt.Sprintf("%%usr: % 2.1f", rand.Float64()*30)))
|
154
|
+
drawRunes(g, 100+6+1, 1+coreid*5+1, []rune(fmt.Sprintf("%%sys: % 2.1f", rand.Float64()*30)))
|
155
|
+
drawRunes(g, 100+6+1, 1+coreid*5+2, []rune(fmt.Sprintf("%%iow: % 2.1f", rand.Float64()*30)))
|
156
|
+
drawRunes(g, 100+6+1, 1+coreid*5+3, []rune(fmt.Sprintf("%%oth: % 2.1f", rand.Float64()*30)))
|
157
|
+
}
|
158
|
+
|
159
|
+
return nil
|
160
|
+
}
|
161
|
+
|
162
|
+
func quit(g *gocui.Gui, v *gocui.View) error {
|
163
|
+
return gocui.ErrQuit
|
164
|
+
}
|
data/core/subsystem/Makefile
CHANGED
@@ -429,3 +429,98 @@ func ReadNetStat(record *StatRecord) error {
|
|
429
429
|
|
430
430
|
return nil
|
431
431
|
}
|
432
|
+
|
433
|
+
func ReadMemStat(record *StatRecord) error {
|
434
|
+
if record == nil {
|
435
|
+
return errors.New("Valid *StatRecord is required.")
|
436
|
+
}
|
437
|
+
|
438
|
+
mem_stat := NewMemStat()
|
439
|
+
|
440
|
+
f, err := os.Open("/proc/meminfo")
|
441
|
+
if err != nil {
|
442
|
+
return err
|
443
|
+
}
|
444
|
+
defer f.Close()
|
445
|
+
scanner := bufio.NewScanner(f)
|
446
|
+
|
447
|
+
for scanner.Scan() {
|
448
|
+
var key string
|
449
|
+
var val int64
|
450
|
+
line := scanner.Text()
|
451
|
+
|
452
|
+
n, err := fmt.Sscanf(line, "%s %d", &key, &val)
|
453
|
+
|
454
|
+
if err == io.EOF {
|
455
|
+
break
|
456
|
+
} else if err != nil {
|
457
|
+
return err
|
458
|
+
}
|
459
|
+
if n != 2 {
|
460
|
+
continue
|
461
|
+
}
|
462
|
+
|
463
|
+
switch key {
|
464
|
+
case "HugePages_Surp:":
|
465
|
+
mem_stat.HugePages_Surp = val
|
466
|
+
case "HugePages_Rsvd:":
|
467
|
+
mem_stat.HugePages_Rsvd = val
|
468
|
+
case "HugePages_Free:":
|
469
|
+
mem_stat.HugePages_Free = val
|
470
|
+
case "HugePages_Total:":
|
471
|
+
mem_stat.HugePages_Total = val
|
472
|
+
case "AnonHugePages:":
|
473
|
+
mem_stat.AnonHugePages = val
|
474
|
+
case "Committed_AS:":
|
475
|
+
mem_stat.Committed_AS = val
|
476
|
+
case "CommitLimit:":
|
477
|
+
mem_stat.CommitLimit = val
|
478
|
+
case "Bounce:":
|
479
|
+
mem_stat.Bounce = val
|
480
|
+
case "NFS_Unstable:":
|
481
|
+
mem_stat.NFS_Unstable = val
|
482
|
+
case "Shmem:":
|
483
|
+
mem_stat.Shmem = val
|
484
|
+
case "Slab:":
|
485
|
+
mem_stat.Slab = val
|
486
|
+
case "SReclaimable:":
|
487
|
+
mem_stat.SReclaimable = val
|
488
|
+
case "SUnreclaim:":
|
489
|
+
mem_stat.SUnreclaim = val
|
490
|
+
case "KernelStack:":
|
491
|
+
mem_stat.KernelStack = val
|
492
|
+
case "PageTables:":
|
493
|
+
mem_stat.PageTables = val
|
494
|
+
case "Mapped:":
|
495
|
+
mem_stat.Mapped = val
|
496
|
+
case "AnonPages:":
|
497
|
+
mem_stat.AnonPages = val
|
498
|
+
case "Writeback:":
|
499
|
+
mem_stat.Writeback = val
|
500
|
+
case "Dirty:":
|
501
|
+
mem_stat.Dirty = val
|
502
|
+
case "SwapFree:":
|
503
|
+
mem_stat.SwapFree = val
|
504
|
+
case "SwapTotal:":
|
505
|
+
mem_stat.SwapTotal = val
|
506
|
+
case "Inactive:":
|
507
|
+
mem_stat.Inactive = val
|
508
|
+
case "Active:":
|
509
|
+
mem_stat.Active = val
|
510
|
+
case "SwapCached:":
|
511
|
+
mem_stat.SwapCached = val
|
512
|
+
case "Cached:":
|
513
|
+
mem_stat.Cached = val
|
514
|
+
case "Buffers:":
|
515
|
+
mem_stat.Buffers = val
|
516
|
+
case "MemFree:":
|
517
|
+
mem_stat.MemFree = val
|
518
|
+
case "MemTotal:":
|
519
|
+
mem_stat.MemTotal = val
|
520
|
+
}
|
521
|
+
}
|
522
|
+
|
523
|
+
record.Mem = mem_stat
|
524
|
+
|
525
|
+
return nil
|
526
|
+
}
|