perfmonger 0.9.0 → 0.10.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/NEWS +29 -2
- data/core/Makefile +26 -25
- data/core/build.sh +20 -13
- data/core/perfmonger-player.go +34 -2
- data/core/perfmonger-plot-formatter.go +2 -4
- data/core/perfmonger-recorder.go +69 -39
- data/core/perfmonger-summarizer.go +17 -1
- data/core/subsystem/perfmonger_linux.go +108 -2
- data/core/subsystem/stat.go +30 -6
- data/core/subsystem/usage.go +76 -0
- data/core/subsystem/usage_test.go +34 -22
- data/core/utils.go +31 -0
- data/lib/perfmonger/command/fingerprint.rb +107 -23
- data/lib/perfmonger/command/plot.rb +18 -7
- data/lib/perfmonger/command/record.rb +17 -6
- data/lib/perfmonger/command/record_option.rb +25 -0
- data/lib/perfmonger/version.rb +1 -1
- data/spec/data/busy100.pgr.gz +0 -0
- data/spec/data/busy100.pgr.played +3 -0
- data/spec/data/busy100.pgr.plot-formatted.cpu.dat +21 -0
- data/spec/data/busy100.pgr.plot-formatted.disk.dat +32 -0
- data/spec/data/busy100.pgr.summary +55 -0
- data/spec/data/busy100.pgr.summary.json +1 -0
- data/spec/play_spec.rb +16 -0
- data/spec/plot_spec.rb +41 -7
- data/spec/summary_spec.rb +30 -0
- metadata +9 -2
@@ -59,7 +59,10 @@ func main() {
|
|
59
59
|
if err != nil {
|
60
60
|
panic(err)
|
61
61
|
}
|
62
|
-
|
62
|
+
defer f.Close()
|
63
|
+
|
64
|
+
input_reader := newPerfmongerLogReader(f)
|
65
|
+
dec := gob.NewDecoder(input_reader)
|
63
66
|
|
64
67
|
err = dec.Decode(&cheader)
|
65
68
|
if err == io.EOF {
|
@@ -103,6 +106,7 @@ func main() {
|
|
103
106
|
lst_record := lst_records[idx]
|
104
107
|
|
105
108
|
var cpu_usage *ss.CpuUsage = nil
|
109
|
+
var intr_usage *ss.InterruptUsage = nil
|
106
110
|
var disk_usage *ss.DiskUsage = nil
|
107
111
|
var net_usage *ss.NetUsage = nil
|
108
112
|
|
@@ -110,6 +114,13 @@ func main() {
|
|
110
114
|
cpu_usage, err = ss.GetCpuUsage(fst_record.Cpu, lst_record.Cpu)
|
111
115
|
}
|
112
116
|
|
117
|
+
if fst_record.Interrupt != nil && lst_record.Interrupt != nil {
|
118
|
+
intr_usage, err = ss.GetInterruptUsage(
|
119
|
+
fst_record.Time, fst_record.Interrupt,
|
120
|
+
lst_record.Time, lst_record.Interrupt,
|
121
|
+
)
|
122
|
+
}
|
123
|
+
|
113
124
|
if fst_record.Disk != nil && lst_record.Disk != nil {
|
114
125
|
disk_usage, err = ss.GetDiskUsage1(
|
115
126
|
fst_record.Time, fst_record.Disk,
|
@@ -134,6 +145,11 @@ func main() {
|
|
134
145
|
cpu_usage.WriteJsonTo(buf)
|
135
146
|
}
|
136
147
|
|
148
|
+
if intr_usage != nil {
|
149
|
+
buf.WriteString(`,"intr":`)
|
150
|
+
intr_usage.WriteJsonTo(buf)
|
151
|
+
}
|
152
|
+
|
137
153
|
if disk_usage != nil {
|
138
154
|
buf.WriteString(`,"disk":`)
|
139
155
|
disk_usage.WriteJsonTo(buf)
|
@@ -8,7 +8,10 @@ import (
|
|
8
8
|
"fmt"
|
9
9
|
"io"
|
10
10
|
"os"
|
11
|
+
"os/exec"
|
11
12
|
"runtime"
|
13
|
+
"strconv"
|
14
|
+
"strings"
|
12
15
|
)
|
13
16
|
|
14
17
|
type PlatformHeader LinuxHeader
|
@@ -92,7 +95,23 @@ func ReadCpuStat(record *StatRecord) error {
|
|
92
95
|
defer f.Close()
|
93
96
|
|
94
97
|
if record.Cpu == nil {
|
95
|
-
|
98
|
+
num_core := 0
|
99
|
+
out, err := exec.Command("nproc", "--all").Output()
|
100
|
+
out_str := strings.TrimSpace(string(out))
|
101
|
+
|
102
|
+
if err == nil {
|
103
|
+
num_core, err = strconv.Atoi(out_str)
|
104
|
+
|
105
|
+
if err != nil {
|
106
|
+
num_core = 0
|
107
|
+
}
|
108
|
+
}
|
109
|
+
|
110
|
+
if num_core == 0 {
|
111
|
+
num_core = runtime.NumCPU()
|
112
|
+
}
|
113
|
+
|
114
|
+
record.Cpu = NewCpuStat(num_core)
|
96
115
|
} else {
|
97
116
|
record.Cpu.Clear()
|
98
117
|
}
|
@@ -200,6 +219,89 @@ func ReadCpuStat(record *StatRecord) error {
|
|
200
219
|
return nil
|
201
220
|
}
|
202
221
|
|
222
|
+
func parseInterruptStatEntry(line string, num_core int) (*InterruptStatEntry, error) {
|
223
|
+
entry := new(InterruptStatEntry)
|
224
|
+
|
225
|
+
entry.NumCore = num_core
|
226
|
+
entry.IntrCounts = make([]int, num_core)
|
227
|
+
|
228
|
+
tokens := strings.Fields(line)
|
229
|
+
|
230
|
+
idx := 0
|
231
|
+
|
232
|
+
tok := tokens[0]
|
233
|
+
tok = strings.TrimRight(tok, ":")
|
234
|
+
if irqno, err := strconv.Atoi(tok); err == nil {
|
235
|
+
entry.IrqNo = irqno
|
236
|
+
entry.IrqType = ""
|
237
|
+
} else {
|
238
|
+
entry.IrqNo = -1
|
239
|
+
entry.IrqType = tok
|
240
|
+
}
|
241
|
+
|
242
|
+
for idx := 1; idx < num_core+1; idx += 1 {
|
243
|
+
var c int
|
244
|
+
var err error
|
245
|
+
|
246
|
+
if idx >= len(tokens) {
|
247
|
+
break
|
248
|
+
}
|
249
|
+
|
250
|
+
tok = tokens[idx]
|
251
|
+
if c, err = strconv.Atoi(tok); err != nil {
|
252
|
+
return nil, errors.New("Invalid string for IntrCounts element: " + tok)
|
253
|
+
}
|
254
|
+
|
255
|
+
entry.IntrCounts[idx-1] = c
|
256
|
+
}
|
257
|
+
|
258
|
+
idx = num_core + 1
|
259
|
+
if idx < len(tokens) {
|
260
|
+
entry.Descr = strings.Join(tokens[idx:], " ")
|
261
|
+
} else {
|
262
|
+
entry.Descr = ""
|
263
|
+
}
|
264
|
+
|
265
|
+
return entry, nil
|
266
|
+
}
|
267
|
+
|
268
|
+
func ReadInterruptStat(record *StatRecord) error {
|
269
|
+
intr_stat := NewInterruptStat()
|
270
|
+
|
271
|
+
if record == nil {
|
272
|
+
return errors.New("Valid *StatRecord is required.")
|
273
|
+
}
|
274
|
+
|
275
|
+
f, err := os.Open("/proc/interrupts")
|
276
|
+
if err != nil {
|
277
|
+
panic(err)
|
278
|
+
}
|
279
|
+
defer f.Close()
|
280
|
+
scan := bufio.NewScanner(f)
|
281
|
+
|
282
|
+
if !scan.Scan() {
|
283
|
+
return errors.New("/proc/interrupts seems to be empty")
|
284
|
+
}
|
285
|
+
|
286
|
+
cores := strings.Fields(scan.Text())
|
287
|
+
num_core := len(cores)
|
288
|
+
|
289
|
+
for scan.Scan() {
|
290
|
+
entry, err := parseInterruptStatEntry(scan.Text(), num_core)
|
291
|
+
|
292
|
+
if err != nil {
|
293
|
+
return err
|
294
|
+
}
|
295
|
+
|
296
|
+
intr_stat.Entries = append(intr_stat.Entries, entry)
|
297
|
+
intr_stat.NumEntries += 1
|
298
|
+
}
|
299
|
+
|
300
|
+
record.Interrupt = intr_stat
|
301
|
+
|
302
|
+
return nil
|
303
|
+
}
|
304
|
+
|
203
305
|
func ReadDiskStats(record *StatRecord, targets *map[string]bool) error {
|
204
306
|
if record == nil {
|
205
307
|
return errors.New("Valid *StatRecord is required.")
|
@@ -294,6 +396,7 @@ func ReadNetStat(record *StatRecord) error {
|
|
294
396
|
case line[0:7] == " face |":
|
295
397
|
continue
|
296
398
|
}
|
399
|
+
line = strings.Replace(line, ":", " ", -1)
|
297
400
|
|
298
401
|
e := NewNetStatEntry()
|
299
402
|
|
@@ -314,7 +417,10 @@ func ReadNetStat(record *StatRecord) error {
|
|
314
417
|
}
|
315
418
|
|
316
419
|
// trim trailing ":" from devname
|
317
|
-
|
420
|
+
if devname[len(devname)-1] == ':' {
|
421
|
+
devname = devname[0 : len(devname)-1]
|
422
|
+
}
|
423
|
+
e.Name = devname
|
318
424
|
|
319
425
|
net_stat.Entries = append(net_stat.Entries, e)
|
320
426
|
}
|
data/core/subsystem/stat.go
CHANGED
@@ -43,6 +43,20 @@ type SoftIrqStat struct {
|
|
43
43
|
Rcu int64
|
44
44
|
}
|
45
45
|
|
46
|
+
type InterruptStatEntry struct {
|
47
|
+
IrqNo int // >0 if associated with devices, -1 if not
|
48
|
+
IrqType string // set intr name if IrqNo == -1
|
49
|
+
|
50
|
+
NumCore int
|
51
|
+
IntrCounts []int
|
52
|
+
Descr string
|
53
|
+
}
|
54
|
+
|
55
|
+
type InterruptStat struct {
|
56
|
+
NumEntries uint
|
57
|
+
Entries []*InterruptStatEntry
|
58
|
+
}
|
59
|
+
|
46
60
|
type DiskStatEntry struct {
|
47
61
|
Major uint
|
48
62
|
Minor uint
|
@@ -89,12 +103,13 @@ type NetStat struct {
|
|
89
103
|
}
|
90
104
|
|
91
105
|
type StatRecord struct {
|
92
|
-
Time
|
93
|
-
Cpu
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
106
|
+
Time time.Time
|
107
|
+
Cpu *CpuStat
|
108
|
+
Interrupt *InterruptStat
|
109
|
+
Proc *ProcStat
|
110
|
+
Disk *DiskStat
|
111
|
+
Softirq *SoftIrqStat
|
112
|
+
Net *NetStat
|
98
113
|
}
|
99
114
|
|
100
115
|
func (core_stat *CpuCoreStat) Clear() {
|
@@ -142,6 +157,14 @@ func (cpu_stat *CpuStat) Clear() {
|
|
142
157
|
}
|
143
158
|
}
|
144
159
|
|
160
|
+
func NewInterruptStat() *InterruptStat {
|
161
|
+
intr_stat := new(InterruptStat)
|
162
|
+
intr_stat.NumEntries = 0
|
163
|
+
intr_stat.Entries = make([]*InterruptStatEntry, 0)
|
164
|
+
|
165
|
+
return intr_stat
|
166
|
+
}
|
167
|
+
|
145
168
|
func NewProcStat() *ProcStat {
|
146
169
|
return &ProcStat{0, 0}
|
147
170
|
}
|
@@ -204,6 +227,7 @@ func NewStatRecord() *StatRecord {
|
|
204
227
|
nil,
|
205
228
|
nil,
|
206
229
|
nil,
|
230
|
+
nil,
|
207
231
|
}
|
208
232
|
}
|
209
233
|
|
data/core/subsystem/usage.go
CHANGED
@@ -29,6 +29,20 @@ type CpuUsage struct {
|
|
29
29
|
CoreUsages []*CpuCoreUsage
|
30
30
|
}
|
31
31
|
|
32
|
+
type CpuCoreIntrUsage struct {
|
33
|
+
Device float64 // intr/sec for devices
|
34
|
+
System float64 // intr/sec for system internal interrupts (timers, TLB miss, ...)
|
35
|
+
}
|
36
|
+
|
37
|
+
type InterruptUsage struct {
|
38
|
+
Interval time.Duration
|
39
|
+
|
40
|
+
NumEntries uint
|
41
|
+
NumCore int
|
42
|
+
|
43
|
+
CoreIntrUsages []*CpuCoreIntrUsage
|
44
|
+
}
|
45
|
+
|
32
46
|
type DiskUsageEntry struct {
|
33
47
|
Interval time.Duration
|
34
48
|
|
@@ -169,6 +183,68 @@ func GetCpuUsage(c1 *CpuStat, c2 *CpuStat) (*CpuUsage, error) {
|
|
169
183
|
return usage, nil
|
170
184
|
}
|
171
185
|
|
186
|
+
func GetInterruptUsage(t1 time.Time, i1 *InterruptStat, t2 time.Time, i2 *InterruptStat) (*InterruptUsage, error) {
|
187
|
+
num_core := i1.Entries[0].NumCore
|
188
|
+
|
189
|
+
usage := new(InterruptUsage)
|
190
|
+
usage.Interval = t2.Sub(t1)
|
191
|
+
usage.NumEntries = i1.NumEntries
|
192
|
+
usage.NumCore = num_core
|
193
|
+
usage.CoreIntrUsages = make([]*CpuCoreIntrUsage, num_core)
|
194
|
+
|
195
|
+
for coreid := 0; coreid < usage.NumCore; coreid += 1 {
|
196
|
+
core_usage := new(CpuCoreIntrUsage)
|
197
|
+
core_usage.Device = 0
|
198
|
+
core_usage.System = 0
|
199
|
+
|
200
|
+
core_dev_count := 0
|
201
|
+
core_sys_count := 0
|
202
|
+
|
203
|
+
for idx, istat_entry1 := range i1.Entries {
|
204
|
+
istat_entry2 := i2.Entries[idx]
|
205
|
+
|
206
|
+
if istat_entry1.IrqNo != istat_entry2.IrqNo ||
|
207
|
+
istat_entry1.IrqType != istat_entry2.IrqType {
|
208
|
+
return nil, errors.New("Intr stat format changed")
|
209
|
+
}
|
210
|
+
|
211
|
+
countup := istat_entry2.IntrCounts[coreid] - istat_entry1.IntrCounts[coreid]
|
212
|
+
if istat_entry1.IrqNo != -1 {
|
213
|
+
core_dev_count += countup
|
214
|
+
} else {
|
215
|
+
core_sys_count += countup
|
216
|
+
}
|
217
|
+
}
|
218
|
+
|
219
|
+
core_usage.Device = float64(core_dev_count) / usage.Interval.Seconds()
|
220
|
+
core_usage.System = float64(core_sys_count) / usage.Interval.Seconds()
|
221
|
+
|
222
|
+
usage.CoreIntrUsages[coreid] = core_usage
|
223
|
+
}
|
224
|
+
|
225
|
+
return usage, nil
|
226
|
+
}
|
227
|
+
|
228
|
+
func (intr_usage *InterruptUsage) WriteJsonTo(buf *bytes.Buffer) {
|
229
|
+
buf.WriteString("{")
|
230
|
+
buf.WriteString(`"core_dev_intr":[`)
|
231
|
+
for idx, core_usage := range intr_usage.CoreIntrUsages {
|
232
|
+
if idx > 0 {
|
233
|
+
buf.WriteString(",")
|
234
|
+
}
|
235
|
+
fmt.Fprintf(buf, "%.2f", core_usage.Device)
|
236
|
+
}
|
237
|
+
buf.WriteString(`],"core_sys_intr":[`)
|
238
|
+
for idx, core_usage := range intr_usage.CoreIntrUsages {
|
239
|
+
if idx > 0 {
|
240
|
+
buf.WriteString(",")
|
241
|
+
}
|
242
|
+
fmt.Fprintf(buf, "%.2f", core_usage.System)
|
243
|
+
}
|
244
|
+
buf.WriteString("]")
|
245
|
+
buf.WriteString("}")
|
246
|
+
}
|
247
|
+
|
172
248
|
func (duentry *DiskUsageEntry) WriteJsonTo(buf *bytes.Buffer) {
|
173
249
|
fmt.Fprintf(buf,
|
174
250
|
`{"riops":%.2f,"wiops":%.2f,"rkbyteps":%.2f,"wkbyteps":%.2f,"rlatency":%.3f,"wlatency":%.3f,"rsize":%.2f,"wsize":%.2f,"qlen":%.2f}`,
|
@@ -65,6 +65,7 @@ func floatEqWithin(val1, val2, epsilon float64) bool {
|
|
65
65
|
|
66
66
|
func TestGetCoreUsage(t *testing.T) {
|
67
67
|
var err error
|
68
|
+
var usage *CpuCoreUsage
|
68
69
|
|
69
70
|
c1 := new(CpuCoreStat)
|
70
71
|
c2 := new(CpuCoreStat)
|
@@ -73,10 +74,22 @@ func TestGetCoreUsage(t *testing.T) {
|
|
73
74
|
c2.User = 5
|
74
75
|
c2.Sys = 5
|
75
76
|
|
76
|
-
// should return error if uptime is the same
|
77
|
-
|
78
|
-
if err
|
79
|
-
t.Error("err
|
77
|
+
// should return usage 0% with no error if uptime is the same
|
78
|
+
usage, err = GetCpuCoreUsage(c1, c2)
|
79
|
+
if err != nil {
|
80
|
+
t.Error("err != nil, want nil")
|
81
|
+
}
|
82
|
+
if !(usage.User == 0 &&
|
83
|
+
usage.Nice == 0 &&
|
84
|
+
usage.Sys == 0 &&
|
85
|
+
usage.Idle == 0 &&
|
86
|
+
usage.Iowait == 0 &&
|
87
|
+
usage.Hardirq == 0 &&
|
88
|
+
usage.Softirq == 0 &&
|
89
|
+
usage.Steal == 0 &&
|
90
|
+
usage.Guest == 0 &&
|
91
|
+
usage.GuestNice == 0) {
|
92
|
+
t.Error("usage is not 0%, want 0%")
|
80
93
|
}
|
81
94
|
|
82
95
|
// should return error if c1.Uptime() is larger than c2.Uptime()
|
@@ -87,7 +100,6 @@ func TestGetCoreUsage(t *testing.T) {
|
|
87
100
|
}
|
88
101
|
|
89
102
|
// should return 75% usr and 25% sys usage
|
90
|
-
var usage *CpuCoreUsage
|
91
103
|
c1.Clear()
|
92
104
|
c2.Clear()
|
93
105
|
c1.User = 100
|
@@ -124,11 +136,11 @@ func TestGetCpuUsage(t *testing.T) {
|
|
124
136
|
c2 := NewCpuStat(num_core)
|
125
137
|
|
126
138
|
usage, err = GetCpuUsage(c1, c2)
|
127
|
-
if err
|
128
|
-
t.Error("Error should be returned
|
139
|
+
if err != nil {
|
140
|
+
t.Error("Error should not be returned")
|
129
141
|
}
|
130
|
-
if usage
|
131
|
-
t.Error("Nil should be returned as usage")
|
142
|
+
if usage == nil {
|
143
|
+
t.Error("Nil should not be returned as usage")
|
132
144
|
}
|
133
145
|
|
134
146
|
c1.CoreStats[0].User = 100
|
@@ -141,13 +153,13 @@ func TestGetCpuUsage(t *testing.T) {
|
|
141
153
|
c2.CoreStats[1].User = c1.CoreStats[0].User + 300
|
142
154
|
c2.CoreStats[1].Sys = c1.CoreStats[0].Sys + 100
|
143
155
|
|
144
|
-
usage, err = GetCpuUsage(c1, c2)
|
145
|
-
if err == nil {
|
146
|
-
|
147
|
-
}
|
148
|
-
if usage != nil {
|
149
|
-
|
150
|
-
}
|
156
|
+
// usage, err = GetCpuUsage(c1, c2)
|
157
|
+
// if err == nil {
|
158
|
+
// t.Error("Error should be returned because no progress in .All uptime")
|
159
|
+
// }
|
160
|
+
// if usage != nil {
|
161
|
+
// t.Error("Nil should be returned as usage")
|
162
|
+
// }
|
151
163
|
|
152
164
|
c1.All.User = 200
|
153
165
|
c1.All.Sys = 100
|
@@ -158,12 +170,12 @@ func TestGetCpuUsage(t *testing.T) {
|
|
158
170
|
c2.CoreStats[0].Clear()
|
159
171
|
|
160
172
|
usage, err = GetCpuUsage(c1, c2)
|
161
|
-
if err == nil {
|
162
|
-
|
163
|
-
}
|
164
|
-
if usage != nil {
|
165
|
-
|
166
|
-
}
|
173
|
+
// if err == nil {
|
174
|
+
// t.Error("Error should be returned because no progress in .CoreStats[9] uptime")
|
175
|
+
// }
|
176
|
+
// if usage != nil {
|
177
|
+
// t.Error("Nil should be returned as usage")
|
178
|
+
// }
|
167
179
|
|
168
180
|
c1.CoreStats[0].User = 100
|
169
181
|
c1.CoreStats[0].Sys = 50
|
data/core/utils.go
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
package main
|
2
|
+
|
3
|
+
import (
|
4
|
+
"bufio"
|
5
|
+
"compress/gzip"
|
6
|
+
"io"
|
7
|
+
)
|
8
|
+
|
9
|
+
func newPerfmongerLogReader(source io.Reader) io.Reader {
|
10
|
+
var ret io.Reader
|
11
|
+
reader := bufio.NewReader(source)
|
12
|
+
|
13
|
+
magic_numbers, e := reader.Peek(2)
|
14
|
+
if e != nil {
|
15
|
+
panic(e)
|
16
|
+
}
|
17
|
+
|
18
|
+
// check magic number
|
19
|
+
if magic_numbers[0] == 0x1f && magic_numbers[1] == 0x8b {
|
20
|
+
// gzipped gob input
|
21
|
+
ret, e = gzip.NewReader(reader)
|
22
|
+
if e != nil {
|
23
|
+
panic(e)
|
24
|
+
}
|
25
|
+
} else {
|
26
|
+
// plain gob input
|
27
|
+
ret = reader
|
28
|
+
}
|
29
|
+
|
30
|
+
return ret
|
31
|
+
}
|