perfmonger 0.10.1 → 0.12.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
}
|