perfmonger 0.6.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 +15 -0
- data/.dir-locals.el +2 -0
- data/.gitignore +4 -0
- data/.rspec +1 -0
- data/.travis.yml +12 -0
- data/COPYING +674 -0
- data/Gemfile +5 -0
- data/HOWTO.md +15 -0
- data/NEWS +115 -0
- data/README.md +61 -0
- data/Rakefile +8 -0
- data/bin/perfmonger +6 -0
- data/data/NOTICE +8 -0
- data/data/Twitter_Bootstrap_LICENSE.txt +176 -0
- data/data/assets/css/bootstrap-responsive.css +1109 -0
- data/data/assets/css/bootstrap.css +6167 -0
- data/data/assets/css/perfmonger.css +17 -0
- data/data/assets/dashboard.erb +319 -0
- data/data/assets/img/glyphicons-halflings-white.png +0 -0
- data/data/assets/img/glyphicons-halflings.png +0 -0
- data/data/assets/js/bootstrap.js +2280 -0
- data/data/assets/js/bootstrap.min.js +6 -0
- data/data/assets/js/canvasjs.js +9042 -0
- data/data/assets/js/canvasjs.min.js +271 -0
- data/data/sysstat.ioconf +268 -0
- data/ext/perfmonger/extconf.rb +19 -0
- data/ext/perfmonger/perfmonger.h +58 -0
- data/ext/perfmonger/perfmonger_record.c +754 -0
- data/ext/perfmonger/sysstat/common.c +627 -0
- data/ext/perfmonger/sysstat/common.h +207 -0
- data/ext/perfmonger/sysstat/ioconf.c +515 -0
- data/ext/perfmonger/sysstat/ioconf.h +84 -0
- data/ext/perfmonger/sysstat/iostat.c +1100 -0
- data/ext/perfmonger/sysstat/iostat.h +121 -0
- data/ext/perfmonger/sysstat/libsysstat.h +19 -0
- data/ext/perfmonger/sysstat/mpstat.c +953 -0
- data/ext/perfmonger/sysstat/mpstat.h +79 -0
- data/ext/perfmonger/sysstat/rd_stats.c +2388 -0
- data/ext/perfmonger/sysstat/rd_stats.h +651 -0
- data/ext/perfmonger/sysstat/sysconfig.h +13 -0
- data/lib/perfmonger/cli.rb +115 -0
- data/lib/perfmonger/command/base_command.rb +39 -0
- data/lib/perfmonger/command/fingerprint.rb +453 -0
- data/lib/perfmonger/command/plot.rb +429 -0
- data/lib/perfmonger/command/record.rb +32 -0
- data/lib/perfmonger/command/record_option.rb +149 -0
- data/lib/perfmonger/command/server.rb +294 -0
- data/lib/perfmonger/command/stat.rb +60 -0
- data/lib/perfmonger/command/stat_option.rb +29 -0
- data/lib/perfmonger/command/summary.rb +402 -0
- data/lib/perfmonger/config.rb +6 -0
- data/lib/perfmonger/version.rb +5 -0
- data/lib/perfmonger.rb +12 -0
- data/misc/release-howto.txt +17 -0
- data/misc/sample-cpu.png +0 -0
- data/misc/sample-read-iops.png +0 -0
- data/perfmonger.gemspec +44 -0
- data/test/run-test.sh +39 -0
- data/test/spec/bin_spec.rb +37 -0
- data/test/spec/data/2devices.expected +42 -0
- data/test/spec/data/2devices.output +42 -0
- data/test/spec/spec_helper.rb +20 -0
- data/test/spec/summary_spec.rb +193 -0
- data/test/test-perfmonger.c +145 -0
- data/test/test.h +9 -0
- metadata +154 -0
@@ -0,0 +1,429 @@
|
|
1
|
+
|
2
|
+
require 'optparse'
|
3
|
+
require 'json'
|
4
|
+
require 'tempfile'
|
5
|
+
require 'tmpdir'
|
6
|
+
|
7
|
+
module PerfMonger
|
8
|
+
module Command
|
9
|
+
|
10
|
+
class PlotCommand < BaseCommand
|
11
|
+
register_command 'plot', "Plot system performance graphs collected by 'record'"
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@parser = OptionParser.new
|
15
|
+
@parser.banner = <<EOS
|
16
|
+
Usage: perfmonger plot [options] LOG_FILE
|
17
|
+
|
18
|
+
Options:
|
19
|
+
EOS
|
20
|
+
|
21
|
+
@data_file = nil
|
22
|
+
@offset_time = 0.0
|
23
|
+
@output_dir = Dir.pwd
|
24
|
+
@output_type = 'pdf'
|
25
|
+
@output_prefix = ''
|
26
|
+
@save_gpfiles = false
|
27
|
+
end
|
28
|
+
|
29
|
+
def parse_args(argv)
|
30
|
+
@parser.on('--offset-time TIME') do |time|
|
31
|
+
@offset_time = Float(time)
|
32
|
+
end
|
33
|
+
|
34
|
+
@parser.on('-o', '--output-dir DIR') do |dir|
|
35
|
+
unless File.directory?(dir)
|
36
|
+
puts("ERROR: no such directory: #{dir}")
|
37
|
+
puts(@parser.help)
|
38
|
+
exit(false)
|
39
|
+
end
|
40
|
+
|
41
|
+
@output_dir = dir
|
42
|
+
end
|
43
|
+
|
44
|
+
@parser.on('-T', '--output-type TYPE', 'Available: pdf, png') do |typ|
|
45
|
+
unless ['pdf', 'png'].include?(typ)
|
46
|
+
puts("ERROR: non supported image type: #{typ}")
|
47
|
+
puts(@parser.help)
|
48
|
+
exit(false)
|
49
|
+
end
|
50
|
+
|
51
|
+
if typ != 'pdf' && ! system('which convert >/dev/null 2>&1')
|
52
|
+
puts("ERROR: convert(1) not found.")
|
53
|
+
puts("ERROR: ImageMagick is required for #{typ}")
|
54
|
+
puts(@parser.help)
|
55
|
+
exit(false)
|
56
|
+
end
|
57
|
+
|
58
|
+
@output_type = typ
|
59
|
+
end
|
60
|
+
|
61
|
+
@parser.on('-p', '--prefix PREFIX',
|
62
|
+
'Output file name prefix.') do |prefix|
|
63
|
+
if ! (prefix =~ /-\Z/)
|
64
|
+
prefix += '-'
|
65
|
+
end
|
66
|
+
|
67
|
+
@output_prefix = prefix
|
68
|
+
end
|
69
|
+
|
70
|
+
@parser.on('-s', '--save',
|
71
|
+
'Save GNUPLOT and data files.') do
|
72
|
+
@save_gpfiles = true
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
@parser.parse!(argv)
|
77
|
+
|
78
|
+
if argv.size == 0
|
79
|
+
puts("ERROR: PerfMonger log file is required")
|
80
|
+
puts(@parser.help)
|
81
|
+
exit(false)
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
@data_file = File.expand_path(argv.shift)
|
86
|
+
end
|
87
|
+
|
88
|
+
def run(argv)
|
89
|
+
parse_args(argv)
|
90
|
+
unless system('which gnuplot >/dev/null 2>&1')
|
91
|
+
puts("ERROR: gnuplot not found")
|
92
|
+
puts(@parser.help)
|
93
|
+
exit(false)
|
94
|
+
end
|
95
|
+
|
96
|
+
unless system('gnuplot -e "set terminal"|grep pdfcairo >/dev/null 2>&1')
|
97
|
+
puts("ERROR: pdfcairo is not supported by installed gnuplot")
|
98
|
+
puts("ERROR: PerfMonger requires pdfcairo-supported gnuplot")
|
99
|
+
puts(@parser.help)
|
100
|
+
exit(false)
|
101
|
+
end
|
102
|
+
|
103
|
+
plot_ioinfo()
|
104
|
+
plot_cpuinfo()
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
def plot_ioinfo()
|
109
|
+
iops_pdf_filename = @output_prefix + 'iops.pdf'
|
110
|
+
transfer_pdf_filename = @output_prefix + 'transfer.pdf'
|
111
|
+
gp_filename = @output_prefix + 'io.gp'
|
112
|
+
dat_filename = @output_prefix + 'io.dat'
|
113
|
+
if @output_type != 'pdf'
|
114
|
+
iops_img_filename = @output_prefix + 'iops.' + @output_type
|
115
|
+
transfer_img_filename = @output_prefix + 'transfer.' + @output_type
|
116
|
+
else
|
117
|
+
iops_img_filename = nil
|
118
|
+
transfer_img_filename = nil
|
119
|
+
end
|
120
|
+
|
121
|
+
Dir.mktmpdir do |working_dir|
|
122
|
+
Dir.chdir(working_dir) do
|
123
|
+
datafile = File.open(dat_filename, 'w')
|
124
|
+
gpfile = File.new(gp_filename, 'w')
|
125
|
+
|
126
|
+
start_time = nil
|
127
|
+
devices = nil
|
128
|
+
|
129
|
+
File.open(@data_file).each_line do |line|
|
130
|
+
record = JSON.parse(line)
|
131
|
+
time = record["time"]
|
132
|
+
ioinfo = record["ioinfo"]
|
133
|
+
return unless ioinfo
|
134
|
+
|
135
|
+
start_time ||= time
|
136
|
+
devices ||= ioinfo["devices"]
|
137
|
+
|
138
|
+
datafile.puts([time - start_time,
|
139
|
+
devices.map{|device|
|
140
|
+
[ioinfo[device]["riops"], ioinfo[device]["wiops"],
|
141
|
+
ioinfo[device]["rsecps"] * 512 / 1024 / 1024, # in MB/s
|
142
|
+
ioinfo[device]["wsecps"] * 512 / 1024 / 1024, # in MB/s
|
143
|
+
]
|
144
|
+
}].flatten.map(&:to_s).join("\t"))
|
145
|
+
end
|
146
|
+
|
147
|
+
datafile.close
|
148
|
+
|
149
|
+
col_idx = 2
|
150
|
+
iops_plot_stmt_list = devices.map do |device|
|
151
|
+
plot_stmt = []
|
152
|
+
plot_stmt.push("\"#{dat_filename}\" usi 1:#{col_idx} with lines lw 2 title \"#{device} read\"")
|
153
|
+
plot_stmt.push("\"#{dat_filename}\" usi 1:#{col_idx + 1} with lines lw 2 title \"#{device} write\"")
|
154
|
+
col_idx += 4
|
155
|
+
plot_stmt
|
156
|
+
end.flatten
|
157
|
+
|
158
|
+
col_idx = 4
|
159
|
+
transfer_plot_stmt_list = devices.map do |device|
|
160
|
+
plot_stmt = []
|
161
|
+
plot_stmt.push("\"#{dat_filename}\" usi 1:#{col_idx} with lines lw 2 title \"#{device} read\"")
|
162
|
+
plot_stmt.push("\"#{dat_filename}\" usi 1:#{col_idx + 1} with lines lw 2 title \"#{device} write\"")
|
163
|
+
col_idx += 4
|
164
|
+
plot_stmt
|
165
|
+
end.flatten
|
166
|
+
|
167
|
+
gpfile.puts <<EOS
|
168
|
+
set term pdfcairo enhanced color
|
169
|
+
set title "IOPS: #{File.basename(@data_file)}"
|
170
|
+
set size 1.0, 1.0
|
171
|
+
set output "#{iops_pdf_filename}"
|
172
|
+
|
173
|
+
set xlabel "elapsed time [sec]"
|
174
|
+
set ylabel "IOPS"
|
175
|
+
|
176
|
+
set grid
|
177
|
+
set xrange [#{@offset_time}:*]
|
178
|
+
set yrange [0:*]
|
179
|
+
|
180
|
+
set key below center
|
181
|
+
|
182
|
+
plot #{iops_plot_stmt_list.join(",\\\n ")}
|
183
|
+
|
184
|
+
|
185
|
+
set title "Transfer rate: #{File.basename(@data_file)}"
|
186
|
+
set output "#{transfer_pdf_filename}"
|
187
|
+
set ylabel "transfer rate [MB/s]"
|
188
|
+
plot #{transfer_plot_stmt_list.join(",\\\n ")}
|
189
|
+
EOS
|
190
|
+
|
191
|
+
gpfile.close
|
192
|
+
|
193
|
+
system("gnuplot #{gpfile.path}")
|
194
|
+
|
195
|
+
if @output_type != 'pdf'
|
196
|
+
system("convert -density 150 -background white #{iops_pdf_filename} #{iops_img_filename}")
|
197
|
+
system("convert -density 150 -background white #{transfer_pdf_filename} #{transfer_img_filename}")
|
198
|
+
end
|
199
|
+
|
200
|
+
end # chdir
|
201
|
+
|
202
|
+
copy_targets = [iops_pdf_filename, transfer_pdf_filename]
|
203
|
+
copy_targets.push(iops_img_filename) if iops_img_filename
|
204
|
+
copy_targets.push(transfer_img_filename) if transfer_img_filename
|
205
|
+
|
206
|
+
if @save_gpfiles
|
207
|
+
copy_targets.push(dat_filename)
|
208
|
+
copy_targets.push(gp_filename)
|
209
|
+
end
|
210
|
+
|
211
|
+
copy_targets.each do |target|
|
212
|
+
FileUtils.copy(File.join(working_dir, target), @output_dir)
|
213
|
+
end
|
214
|
+
end # mktempdir
|
215
|
+
end # def
|
216
|
+
|
217
|
+
def plot_cpuinfo()
|
218
|
+
pdf_filename = @output_prefix + 'cpu.pdf'
|
219
|
+
gp_filename = @output_prefix + 'cpu.gp'
|
220
|
+
dat_filename = @output_prefix + 'cpu.dat'
|
221
|
+
|
222
|
+
all_pdf_filename = @output_prefix + 'allcpu.pdf'
|
223
|
+
all_gp_filename = @output_prefix + 'allcpu.gp'
|
224
|
+
all_dat_filename = @output_prefix + 'allcpu.dat'
|
225
|
+
|
226
|
+
if @output_type != 'pdf'
|
227
|
+
img_filename = @output_prefix + 'cpu.' + @output_type
|
228
|
+
all_img_filename = @output_prefix + 'allcpu.' + @output_type
|
229
|
+
else
|
230
|
+
img_filename = nil
|
231
|
+
all_img_filename = nil
|
232
|
+
end
|
233
|
+
|
234
|
+
Dir.mktmpdir do |working_dir|
|
235
|
+
Dir.chdir(working_dir) do
|
236
|
+
datafile = File.open(dat_filename, 'w')
|
237
|
+
gpfile = File.open(gp_filename, 'w')
|
238
|
+
all_datafile = File.open(all_dat_filename, 'w')
|
239
|
+
all_gpfile = File.open(all_gp_filename, 'w')
|
240
|
+
|
241
|
+
start_time = nil
|
242
|
+
end_time = 0
|
243
|
+
devices = nil
|
244
|
+
nr_cpu = nil
|
245
|
+
|
246
|
+
File.open(@data_file).each_line do |line|
|
247
|
+
record = JSON.parse(line)
|
248
|
+
|
249
|
+
time = record["time"]
|
250
|
+
cpuinfo = record["cpuinfo"]
|
251
|
+
return unless cpuinfo
|
252
|
+
nr_cpu = cpuinfo['nr_cpu']
|
253
|
+
|
254
|
+
cores = cpuinfo['cpus']
|
255
|
+
|
256
|
+
start_time ||= time
|
257
|
+
end_time = [end_time, time].max
|
258
|
+
|
259
|
+
datafile.puts([time - start_time,
|
260
|
+
%w|usr nice sys iowait irq soft steal guest idle|.map do |key|
|
261
|
+
cores.map{|core| core[key]}.inject(&:+)
|
262
|
+
end].flatten.map(&:to_s).join("\t"))
|
263
|
+
end
|
264
|
+
datafile.close
|
265
|
+
|
266
|
+
col_idx = 2
|
267
|
+
columns = []
|
268
|
+
plot_stmt_list = []
|
269
|
+
%w|%usr %nice %sys %iowait %irq %soft %steal %guest|.each do |key|
|
270
|
+
columns << col_idx
|
271
|
+
plot_stmt = "\"#{datafile.path}\" usi 1:(#{columns.map{|i| "$#{i}"}.join("+")}) with filledcurve x1 lw 0 lc #{col_idx - 1} title \"#{key}\""
|
272
|
+
plot_stmt_list << plot_stmt
|
273
|
+
col_idx += 1
|
274
|
+
end
|
275
|
+
|
276
|
+
pdf_file = File.join(@output_dir, "cpu.pdf")
|
277
|
+
gpfile.puts <<EOS
|
278
|
+
set term pdfcairo enhanced color
|
279
|
+
set title "CPU usage: #{File.basename(@data_file)} (max: #{nr_cpu*100}%)"
|
280
|
+
set output "#{pdf_filename}"
|
281
|
+
set key outside center bottom horizontal
|
282
|
+
set size 1.0, 1.0
|
283
|
+
|
284
|
+
set xlabel "elapsed time [sec]"
|
285
|
+
set ylabel "CPU usage"
|
286
|
+
|
287
|
+
set grid
|
288
|
+
set xrange [#{@offset_time}:#{end_time - start_time}]
|
289
|
+
set yrange [0:*]
|
290
|
+
|
291
|
+
plot #{plot_stmt_list.reverse.join(",\\\n ")}
|
292
|
+
EOS
|
293
|
+
|
294
|
+
gpfile.close
|
295
|
+
system("gnuplot #{gpfile.path}")
|
296
|
+
|
297
|
+
if @output_type != 'pdf'
|
298
|
+
system("convert -density 150 -background white #{pdf_filename} #{img_filename}")
|
299
|
+
end
|
300
|
+
|
301
|
+
## Plot all CPUs in a single file
|
302
|
+
|
303
|
+
nr_cpu_factors = factors(nr_cpu)
|
304
|
+
nr_cols = nr_cpu_factors.select do |x|
|
305
|
+
x <= Math.sqrt(nr_cpu)
|
306
|
+
end.max
|
307
|
+
nr_cols ||= Math.sqrt(nr_cpu).ceil
|
308
|
+
nr_rows = nr_cpu / nr_cols
|
309
|
+
|
310
|
+
all_gpfile.puts <<EOS
|
311
|
+
set term pdfcairo color enhanced size 8.5inch, 11inch
|
312
|
+
set output "#{all_pdf_filename}"
|
313
|
+
set size 1.0, 1.0
|
314
|
+
set multiplot
|
315
|
+
set grid
|
316
|
+
set xrange [#{@offset_time}:#{end_time - start_time}]
|
317
|
+
set yrange [0:101]
|
318
|
+
|
319
|
+
EOS
|
320
|
+
|
321
|
+
legend_height = 0.04
|
322
|
+
nr_cpu.times do |cpu_idx|
|
323
|
+
all_datafile.puts("# cpu #{cpu_idx}")
|
324
|
+
File.open(@data_file).each_line do |line|
|
325
|
+
record = JSON.parse(line)
|
326
|
+
time = record["time"]
|
327
|
+
cpurec = record["cpuinfo"]["cpus"][cpu_idx]
|
328
|
+
all_datafile.puts([time - start_time,
|
329
|
+
cpurec["usr"] + cpurec["nice"],
|
330
|
+
cpurec["sys"],
|
331
|
+
cpurec["irq"],
|
332
|
+
cpurec["soft"],
|
333
|
+
cpurec["steal"] + cpurec["guest"],
|
334
|
+
cpurec["iowait"]].map(&:to_s).join("\t"))
|
335
|
+
end
|
336
|
+
all_datafile.puts("")
|
337
|
+
all_datafile.puts("")
|
338
|
+
|
339
|
+
xpos = (1.0 / nr_cols) * (cpu_idx % nr_cols)
|
340
|
+
ypos = ((1.0 - legend_height) / nr_rows) * (nr_rows - 1 - (cpu_idx / nr_cols).to_i) + legend_height
|
341
|
+
|
342
|
+
all_gpfile.puts <<EOS
|
343
|
+
set title 'cpu #{cpu_idx}' offset 0.0,-0.7 font 'Arial,16'
|
344
|
+
unset key
|
345
|
+
set origin #{xpos}, #{ypos}
|
346
|
+
set size #{1.0/nr_cols}, #{(1.0 - legend_height)/nr_rows}
|
347
|
+
set rmargin 0.5
|
348
|
+
set lmargin 3.5
|
349
|
+
set tmargin 1.3
|
350
|
+
set bmargin 1.3
|
351
|
+
set xtics offset 0.0,0.5
|
352
|
+
set ytics offset 0.5,0
|
353
|
+
set style fill noborder
|
354
|
+
plot '#{all_datafile.path}' index #{cpu_idx} using 1:($2+$3+$4+$5+$6+$7) with filledcurve x1 lw 0 lc 6 title '%iowait', \\
|
355
|
+
'#{all_datafile.path}' index #{cpu_idx} using 1:($2+$3+$4+$5+$6) with filledcurve x1 lw 0 lc 5 title '%other', \\
|
356
|
+
'#{all_datafile.path}' index #{cpu_idx} using 1:($2+$3+$4+$5) with filledcurve x1 lw 0 lc 4 title '%soft', \\
|
357
|
+
'#{all_datafile.path}' index #{cpu_idx} using 1:($2+$3+$4) with filledcurve x1 lw 0 lc 3 title '%irq', \\
|
358
|
+
'#{all_datafile.path}' index #{cpu_idx} using 1:($2+$3) with filledcurve x1 lw 0 lc 2 title '%sys', \\
|
359
|
+
'#{all_datafile.path}' index #{cpu_idx} using 1:2 with filledcurve x1 lw 0 lc 1 title '%usr'
|
360
|
+
|
361
|
+
EOS
|
362
|
+
|
363
|
+
end
|
364
|
+
|
365
|
+
all_gpfile.puts <<EOS
|
366
|
+
unset title
|
367
|
+
set key center center horizontal font "Arial,16"
|
368
|
+
set origin 0.0, 0.0
|
369
|
+
set size 1.0, #{legend_height}
|
370
|
+
set rmargin 0
|
371
|
+
set lmargin 0
|
372
|
+
set tmargin 0
|
373
|
+
set bmargin 0
|
374
|
+
unset tics
|
375
|
+
set border 0
|
376
|
+
set yrange [0:1]
|
377
|
+
# plot -1 with filledcurve x1 title '%usr'
|
378
|
+
|
379
|
+
plot -1 with filledcurve x1 lw 0 lc 1 title '%usr', \\
|
380
|
+
-1 with filledcurve x1 lw 0 lc 2 title '%sys', \\
|
381
|
+
-1 with filledcurve x1 lw 0 lc 3 title '%irq', \\
|
382
|
+
-1 with filledcurve x1 lw 0 lc 4 title '%soft', \\
|
383
|
+
-1 with filledcurve x1 lw 0 lc 5 title '%other', \\
|
384
|
+
-1 with filledcurve x1 lw 0 lc 6 title '%iowait'
|
385
|
+
EOS
|
386
|
+
|
387
|
+
all_datafile.fsync
|
388
|
+
all_gpfile.fsync
|
389
|
+
all_datafile.close
|
390
|
+
all_gpfile.close
|
391
|
+
|
392
|
+
system("gnuplot #{all_gpfile.path}")
|
393
|
+
|
394
|
+
if @output_type != 'pdf'
|
395
|
+
system("convert -density 150 -background white #{all_pdf_filename} #{all_img_filename}")
|
396
|
+
end
|
397
|
+
|
398
|
+
end # chdir
|
399
|
+
|
400
|
+
copy_targets = []
|
401
|
+
|
402
|
+
copy_targets << pdf_filename
|
403
|
+
copy_targets << img_filename if img_filename
|
404
|
+
copy_targets << all_pdf_filename
|
405
|
+
copy_targets << all_img_filename if all_img_filename
|
406
|
+
|
407
|
+
if @save_gpfiles
|
408
|
+
copy_targets << gp_filename
|
409
|
+
copy_targets << dat_filename
|
410
|
+
copy_targets << all_gp_filename
|
411
|
+
copy_targets << all_dat_filename
|
412
|
+
end
|
413
|
+
|
414
|
+
copy_targets.each do |target|
|
415
|
+
FileUtils.copy(File.join(working_dir, target), @output_dir)
|
416
|
+
end
|
417
|
+
end # mktempdir
|
418
|
+
end # def
|
419
|
+
|
420
|
+
private
|
421
|
+
def factors(n)
|
422
|
+
(2..([n, n / 2].max).to_i).select do |x|
|
423
|
+
n % x == 0
|
424
|
+
end.sort
|
425
|
+
end
|
426
|
+
end
|
427
|
+
|
428
|
+
end # module Command
|
429
|
+
end # module PerfMonger
|
@@ -0,0 +1,32 @@
|
|
1
|
+
|
2
|
+
require 'optparse'
|
3
|
+
require 'json'
|
4
|
+
require 'tempfile'
|
5
|
+
require 'tmpdir'
|
6
|
+
|
7
|
+
module PerfMonger
|
8
|
+
module Command
|
9
|
+
|
10
|
+
class RecordCommand < BaseCommand
|
11
|
+
register_command 'record', 'Record system performance information'
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
super
|
15
|
+
end
|
16
|
+
|
17
|
+
def run(argv)
|
18
|
+
@argv, @option = PerfMonger::Command::RecordOption.parse(argv)
|
19
|
+
|
20
|
+
exec_record_cmd()
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
def exec_record_cmd()
|
25
|
+
cmd = @option.make_command
|
26
|
+
|
27
|
+
Process.exec(*cmd)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end # module Command
|
32
|
+
end # module PerfMonger
|
@@ -0,0 +1,149 @@
|
|
1
|
+
|
2
|
+
module PerfMonger
|
3
|
+
module Command
|
4
|
+
|
5
|
+
class RecordOption
|
6
|
+
attr_reader :devices
|
7
|
+
attr_reader :interval
|
8
|
+
attr_reader :verbose
|
9
|
+
attr_reader :report_cpu
|
10
|
+
attr_reader :report_io
|
11
|
+
attr_reader :report_ctx_switch
|
12
|
+
attr_reader :logfile
|
13
|
+
|
14
|
+
attr_reader :parser
|
15
|
+
|
16
|
+
def self.parse(argv)
|
17
|
+
option = self.new
|
18
|
+
argv = option.parse(argv)
|
19
|
+
|
20
|
+
return argv, option
|
21
|
+
end
|
22
|
+
|
23
|
+
def parse(argv)
|
24
|
+
argv = @parser.parse(argv)
|
25
|
+
|
26
|
+
if ! @report_io && ! @report_ctx_switch && ! @report_cpu
|
27
|
+
@report_cpu = true
|
28
|
+
@report_io = true
|
29
|
+
@all_devices = true
|
30
|
+
end
|
31
|
+
|
32
|
+
argv
|
33
|
+
end
|
34
|
+
|
35
|
+
def make_command
|
36
|
+
# try to search perfmonger-record in build environment
|
37
|
+
# then search installed directory
|
38
|
+
record_bin = [File.expand_path("../../perfmonger_record", __FILE__),
|
39
|
+
File.expand_path("lib/perfmonger/perfmonger_record", PerfMonger::ROOTDIR),
|
40
|
+
File.expand_path("ext/perfmonger/perfmonger_record", PerfMonger::ROOTDIR)].find do |bin|
|
41
|
+
File.executable?(bin)
|
42
|
+
end
|
43
|
+
|
44
|
+
if record_bin == nil || ! File.executable?(record_bin)
|
45
|
+
puts("ERROR: perfmonger-record(1) not found!")
|
46
|
+
exit(false)
|
47
|
+
end
|
48
|
+
|
49
|
+
cmd = [record_bin]
|
50
|
+
cmd << '-i'
|
51
|
+
cmd << @interval.to_s
|
52
|
+
if @interval_backoff
|
53
|
+
cmd << '-b'
|
54
|
+
end
|
55
|
+
if @start_delay > 0
|
56
|
+
cmd << '-s'
|
57
|
+
cmd << @start_delay.to_s
|
58
|
+
end
|
59
|
+
if @timeout
|
60
|
+
cmd << '-t'
|
61
|
+
cmd << @timeout.to_s
|
62
|
+
end
|
63
|
+
cmd << '-C' if @report_cpu
|
64
|
+
cmd << '-S' if @report_ctx_switch
|
65
|
+
cmd << '-l' if @logfile != STDOUT
|
66
|
+
cmd << @logfile if @logfile != STDOUT
|
67
|
+
if @report_io
|
68
|
+
if @all_devices
|
69
|
+
cmd << '-D'
|
70
|
+
else
|
71
|
+
@devices.each do |device|
|
72
|
+
cmd << '-d'
|
73
|
+
cmd << device
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
cmd << '-v' if @verbose
|
78
|
+
|
79
|
+
cmd
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
def initialize
|
84
|
+
@devices = []
|
85
|
+
@all_devices = false
|
86
|
+
@interval = 1.0 # in second
|
87
|
+
@interval_backoff = true
|
88
|
+
@start_delay = 0.0 # in second
|
89
|
+
@timeout = nil # in second, or nil (= no timeout)
|
90
|
+
@verbose = false
|
91
|
+
@report_cpu = false
|
92
|
+
@report_io = false
|
93
|
+
@report_ctx_switch = false
|
94
|
+
@logfile = STDOUT
|
95
|
+
|
96
|
+
@parser = OptionParser.new
|
97
|
+
|
98
|
+
@parser.on('-d', '--device DEVICE',
|
99
|
+
'Device name to be monitored (e.g. sda, sdb, md0, dm-1).') do |device|
|
100
|
+
@devices.push(device)
|
101
|
+
@report_io = true
|
102
|
+
end
|
103
|
+
|
104
|
+
@parser.on('-D', '--all-devices',
|
105
|
+
'Monitor all block devices.') do
|
106
|
+
@all_devices = true
|
107
|
+
@report_io = true
|
108
|
+
end
|
109
|
+
|
110
|
+
@parser.on('-i', '--interval SEC',
|
111
|
+
'Amount of time between each measurement report. Floating point is o.k.') do |interval|
|
112
|
+
@interval = Float(interval)
|
113
|
+
end
|
114
|
+
|
115
|
+
@parser.on('-B', '--no-interval-backoff',
|
116
|
+
'Prevent interval to be set longer every after 100 records.') do
|
117
|
+
@interval_backoff = false
|
118
|
+
end
|
119
|
+
|
120
|
+
@parser.on('-s', '--start-delay SEC',
|
121
|
+
'Amount of wait time before starting measurement. Floating point is o.k.') do |start_delay|
|
122
|
+
@start_delay = Float(start_delay)
|
123
|
+
end
|
124
|
+
|
125
|
+
@parser.on('-t', '--timeout SEC',
|
126
|
+
'Amount of measurement time. Floating point is o.k.') do |timeout|
|
127
|
+
@timeout = Float(timeout)
|
128
|
+
end
|
129
|
+
|
130
|
+
@parser.on('-C', '--cpu', 'Report CPU usage.') do
|
131
|
+
@report_cpu = true
|
132
|
+
end
|
133
|
+
|
134
|
+
@parser.on('-S', '--context-switch', 'Report context switches per sec.') do
|
135
|
+
@report_ctx_switch = true
|
136
|
+
end
|
137
|
+
|
138
|
+
@parser.on('-l', '--logfile FILE') do |file|
|
139
|
+
@logfile = file
|
140
|
+
end
|
141
|
+
|
142
|
+
@parser.on('-v', '--verbose') do
|
143
|
+
@verbose = true
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
end # module Command
|
149
|
+
end # module PerfMonger
|