perfmonger 0.9.0 → 0.10.1
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 +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
|
+
}
|