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.
Files changed (66) hide show
  1. checksums.yaml +15 -0
  2. data/.dir-locals.el +2 -0
  3. data/.gitignore +4 -0
  4. data/.rspec +1 -0
  5. data/.travis.yml +12 -0
  6. data/COPYING +674 -0
  7. data/Gemfile +5 -0
  8. data/HOWTO.md +15 -0
  9. data/NEWS +115 -0
  10. data/README.md +61 -0
  11. data/Rakefile +8 -0
  12. data/bin/perfmonger +6 -0
  13. data/data/NOTICE +8 -0
  14. data/data/Twitter_Bootstrap_LICENSE.txt +176 -0
  15. data/data/assets/css/bootstrap-responsive.css +1109 -0
  16. data/data/assets/css/bootstrap.css +6167 -0
  17. data/data/assets/css/perfmonger.css +17 -0
  18. data/data/assets/dashboard.erb +319 -0
  19. data/data/assets/img/glyphicons-halflings-white.png +0 -0
  20. data/data/assets/img/glyphicons-halflings.png +0 -0
  21. data/data/assets/js/bootstrap.js +2280 -0
  22. data/data/assets/js/bootstrap.min.js +6 -0
  23. data/data/assets/js/canvasjs.js +9042 -0
  24. data/data/assets/js/canvasjs.min.js +271 -0
  25. data/data/sysstat.ioconf +268 -0
  26. data/ext/perfmonger/extconf.rb +19 -0
  27. data/ext/perfmonger/perfmonger.h +58 -0
  28. data/ext/perfmonger/perfmonger_record.c +754 -0
  29. data/ext/perfmonger/sysstat/common.c +627 -0
  30. data/ext/perfmonger/sysstat/common.h +207 -0
  31. data/ext/perfmonger/sysstat/ioconf.c +515 -0
  32. data/ext/perfmonger/sysstat/ioconf.h +84 -0
  33. data/ext/perfmonger/sysstat/iostat.c +1100 -0
  34. data/ext/perfmonger/sysstat/iostat.h +121 -0
  35. data/ext/perfmonger/sysstat/libsysstat.h +19 -0
  36. data/ext/perfmonger/sysstat/mpstat.c +953 -0
  37. data/ext/perfmonger/sysstat/mpstat.h +79 -0
  38. data/ext/perfmonger/sysstat/rd_stats.c +2388 -0
  39. data/ext/perfmonger/sysstat/rd_stats.h +651 -0
  40. data/ext/perfmonger/sysstat/sysconfig.h +13 -0
  41. data/lib/perfmonger/cli.rb +115 -0
  42. data/lib/perfmonger/command/base_command.rb +39 -0
  43. data/lib/perfmonger/command/fingerprint.rb +453 -0
  44. data/lib/perfmonger/command/plot.rb +429 -0
  45. data/lib/perfmonger/command/record.rb +32 -0
  46. data/lib/perfmonger/command/record_option.rb +149 -0
  47. data/lib/perfmonger/command/server.rb +294 -0
  48. data/lib/perfmonger/command/stat.rb +60 -0
  49. data/lib/perfmonger/command/stat_option.rb +29 -0
  50. data/lib/perfmonger/command/summary.rb +402 -0
  51. data/lib/perfmonger/config.rb +6 -0
  52. data/lib/perfmonger/version.rb +5 -0
  53. data/lib/perfmonger.rb +12 -0
  54. data/misc/release-howto.txt +17 -0
  55. data/misc/sample-cpu.png +0 -0
  56. data/misc/sample-read-iops.png +0 -0
  57. data/perfmonger.gemspec +44 -0
  58. data/test/run-test.sh +39 -0
  59. data/test/spec/bin_spec.rb +37 -0
  60. data/test/spec/data/2devices.expected +42 -0
  61. data/test/spec/data/2devices.output +42 -0
  62. data/test/spec/spec_helper.rb +20 -0
  63. data/test/spec/summary_spec.rb +193 -0
  64. data/test/test-perfmonger.c +145 -0
  65. data/test/test.h +9 -0
  66. metadata +154 -0
@@ -0,0 +1,115 @@
1
+
2
+ require 'optparse'
3
+
4
+ module PerfMonger
5
+ module CLI
6
+
7
+ class Runner
8
+ def self.register_command(command_name, klass)
9
+ @@commands ||= Hash.new
10
+ @@aliases ||= Hash.new
11
+
12
+ @@commands[command_name] = klass
13
+ end
14
+
15
+ def self.register_alias(alias_name, command_name)
16
+ if @@commands.nil?
17
+ raise RuntimeError.new("No command is registered yet.")
18
+ end
19
+
20
+ if ! @@commands.has_key?(command_name)
21
+ raise RuntimeError.new("Command '#{command_name}' is not registered.")
22
+ end
23
+
24
+ @@aliases[alias_name] = command_name
25
+ end
26
+
27
+ def initialize
28
+
29
+ end
30
+
31
+ def run(argv = ARGV)
32
+ parser = OptionParser.new
33
+ parser.banner = <<EOS
34
+ Usage: #{File.basename($0)} [options] COMMAND [args]
35
+
36
+ EOS
37
+
38
+ ## make list of subcommands
39
+ commands = @@commands.values.sort_by do |command|
40
+ # important command first: sort by [priority, name]
41
+ command_name = command.command_name
42
+ case command_name
43
+ when "record"
44
+ [0, command_name]
45
+ when "stat"
46
+ [1, command_name]
47
+ when "plot"
48
+ [2, command_name]
49
+ else
50
+ [999, command_name]
51
+ end
52
+ end
53
+
54
+ max_len = commands.map(&:command_name).map(&:size).max
55
+ command_list_str = commands.map do |command|
56
+ # pad command names
57
+ command_name = command.command_name
58
+ command_name = command_name + (" " * (max_len - command_name.size))
59
+
60
+ str = " " + command_name + " " + command.description
61
+
62
+ if command.aliases && command.aliases.size > 0
63
+ str += "\n" + " " + (" " * max_len) + " " +
64
+ "Aliases: " + command.aliases.join(", ")
65
+ end
66
+
67
+ str
68
+ end.join("\n")
69
+
70
+ subcommand_list = <<EOS
71
+
72
+ Commands:
73
+ #{command_list_str}
74
+ EOS
75
+
76
+ parser.summary_indent = " "
77
+
78
+ parser.on('-h', '--help', 'Show this help') do
79
+ puts(parser.help)
80
+ puts(subcommand_list)
81
+ exit(true)
82
+ end
83
+
84
+ parser.on('-v', '--version', 'Show version number') do
85
+ puts("PerfMonger version " + PerfMonger::VERSION)
86
+ exit(true)
87
+ end
88
+
89
+ parser.order!(argv)
90
+
91
+ if argv.size == 0
92
+ puts(parser.help)
93
+ puts(subcommand_list)
94
+ exit(false)
95
+ end
96
+
97
+ command_name = argv.shift
98
+
99
+ if @@aliases[command_name]
100
+ command_name = @@aliases[command_name]
101
+ end
102
+ command_class = @@commands[command_name]
103
+
104
+ unless command_class
105
+ puts("No such command: #{command_name}")
106
+ puts(subcommand_list)
107
+ exit(false)
108
+ end
109
+
110
+ command_class.new.run(argv)
111
+ end
112
+ end
113
+
114
+ end
115
+ end
@@ -0,0 +1,39 @@
1
+
2
+ module PerfMonger
3
+ module Command
4
+
5
+ class BaseCommand
6
+ class << self
7
+ attr_accessor :command_name
8
+ attr_accessor :description
9
+ attr_accessor :aliases
10
+
11
+ def register_command(command_name, description = "")
12
+ PerfMonger::CLI::Runner.register_command(command_name, self)
13
+ self.command_name = command_name
14
+ self.description = description
15
+ end
16
+
17
+ def register_alias(alias_name)
18
+ if self.command_name
19
+ RuntimeError.new("#{self} does not have registered command name.")
20
+ end
21
+
22
+ self.aliases ||= []
23
+ self.aliases.push(alias_name)
24
+ PerfMonger::CLI::Runner.register_alias(alias_name, self.command_name)
25
+ end
26
+ end
27
+
28
+ def initialize
29
+ @parser = OptionParser.new
30
+ @parser.banner = <<EOS
31
+ Usage: #{File.basename($0)} #{self.class.command_name} [options]
32
+
33
+ Options:
34
+ EOS
35
+ end
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,453 @@
1
+
2
+ require 'fileutils'
3
+ require 'tmpdir'
4
+ require 'tempfile'
5
+
6
+ module PerfMonger
7
+ module Command
8
+
9
+ class FingerprintCommand < BaseCommand
10
+ register_command 'fingerprint', 'Gather all possible system config information'
11
+ register_alias 'bukko'
12
+ register_alias 'fp'
13
+
14
+ def initialize
15
+ @parser = OptionParser.new
16
+ @parser.banner = <<EOS
17
+ Usage: perfmonger fingerprint [options] OUTPUT_TARBALL
18
+
19
+ Options:
20
+ EOS
21
+
22
+ hostname = `hostname`.strip
23
+ @output_tarball = "./fingerprint.#{hostname}.tar.gz"
24
+ end
25
+
26
+ def parse_args(argv)
27
+ @parser.parse!(argv)
28
+
29
+ if argv.size == 0
30
+ puts("ERROR: output directory is required.")
31
+ puts(@parser.help)
32
+ exit(false)
33
+ end
34
+
35
+ @output_tarball = argv.shift
36
+
37
+ if ! @output_tarball =~ /\.(tar\.gz|tgz)$/
38
+ @output_tarball += ".tar.gz"
39
+ end
40
+ end
41
+
42
+ def run(argv)
43
+ parse_args(argv)
44
+
45
+ ENV['LANG'] = 'C'
46
+
47
+ $stderr.puts("System information is gathered into #{@output_tarball}")
48
+
49
+ Dir.mktmpdir do |tmpdir|
50
+ output_basename = File.basename(@output_tarball.gsub(/\.(tar\.gz|tgz)$/, ''))
51
+
52
+ @output_dir = File.join(tmpdir, output_basename)
53
+ FileUtils.mkdir(@output_dir)
54
+
55
+ ## Collect generic info.
56
+ do_with_message("Saving /proc info") do
57
+ save_proc_info()
58
+ end
59
+
60
+ do_with_message("Saving IRQ info") do
61
+ save_irq_info()
62
+ end
63
+
64
+ do_with_message("Saving block device info") do
65
+ save_device_info()
66
+ end
67
+
68
+ do_with_message("Saving /dev/disk info") do
69
+ save_disk_info()
70
+ end
71
+
72
+ do_with_message("Saving PCI/PCIe info") do
73
+ save_pci_info()
74
+ end
75
+
76
+ do_with_message("Saving kernel module info") do
77
+ save_module_info()
78
+ end
79
+
80
+ do_with_message("Saving distro info") do
81
+ save_distro_info()
82
+ end
83
+
84
+ do_with_message("Saving sysctl info") do
85
+ save_sysctl_info()
86
+ end
87
+
88
+ do_with_message("Saving dmidecode info") do
89
+ save_dmidecode_info()
90
+ end
91
+
92
+
93
+ ## Collect vendor specific info
94
+
95
+ # LSI MegaRAID
96
+ megacli_bin = "/opt/MegaRAID/MegaCli/MegaCli64"
97
+ if File.executable?(megacli_bin) && Process::UID.rid == 0
98
+ do_with_message("Saving MegaRAID settings") do
99
+ save_megaraid_info(megacli_bin)
100
+ end
101
+ end
102
+
103
+ tmptar_path = Tempfile.new("fingerprint").path
104
+
105
+ Dir.chdir(tmpdir) do
106
+ if ! system("tar czf '#{tmptar_path}' #{output_basename}")
107
+ raise RuntimeError.new("Failed to execute tar(1)")
108
+ end
109
+ end
110
+
111
+ FileUtils.mv(tmptar_path, @output_tarball)
112
+ end
113
+ end
114
+
115
+ private
116
+ def do_with_message(message)
117
+ $stderr.print(message + " ... ")
118
+ $stderr.flush
119
+
120
+ @errors = []
121
+
122
+ begin
123
+ yield
124
+ rescue StandardError => err
125
+ $stderr.puts("failed")
126
+ @errors.push(err)
127
+ end
128
+
129
+ if @errors.empty?
130
+ $stderr.puts("done")
131
+ $stderr.puts("")
132
+ else
133
+ $stderr.puts("failed")
134
+ $stderr.puts("")
135
+ @errors.each do |error|
136
+ $stderr.puts(" ERROR: #{error.message}")
137
+ end
138
+ $stderr.puts("")
139
+ end
140
+ end
141
+
142
+ def read_file(src)
143
+ if File.exists?(src)
144
+ begin
145
+ return File.read(src)
146
+ rescue StandardError => err
147
+ @errors.push(err)
148
+ end
149
+ end
150
+
151
+ nil
152
+ end
153
+
154
+ def copy_file(src, dest)
155
+ content = read_file(src)
156
+
157
+ if content
158
+ File.open(dest, "w") do |f|
159
+ f.print(content)
160
+ end
161
+ end
162
+ end
163
+
164
+ def find_executable(command_name)
165
+ # try to find lspci
166
+ dirs = ["/sbin", "/usr/sbin", "/usr/local/sbin", "/usr/bin", "/usr/local/bin"]
167
+ dirs += ENV['PATH'].split(":")
168
+
169
+ bindir = dirs.find do |dir|
170
+ File.executable?(File.expand_path(command_name, dir))
171
+ end
172
+
173
+ if bindir
174
+ File.expand_path(command_name, bindir)
175
+ else
176
+ @errors << RuntimeError.new("#{command_name}(1) not found")
177
+ nil
178
+ end
179
+ end
180
+
181
+
182
+ def save_proc_info()
183
+ ["cpuinfo", "meminfo", "mdstat", "mounts", "interrupts",
184
+ "diskstats", "partitions", "ioports",
185
+ ].each do |entry|
186
+ copy_file("/proc/#{entry}", "#{@output_dir}/proc-#{entry}.log")
187
+ end
188
+
189
+ copy_file('/proc/scsi/scsi', "#{@output_dir}/proc-scsi.log")
190
+
191
+ File.open("#{@output_dir}/proc-sys-fs.log", "w") do |f|
192
+ Dir.glob("/proc/sys/fs/*").each do |path|
193
+ next unless File.file?(path)
194
+ begin
195
+ content = File.read(path)
196
+ rescue Errno::EACCES => err
197
+ @errors.push(err)
198
+ f.puts("## #{path}")
199
+ f.puts("permission denied")
200
+ f.puts("")
201
+ next
202
+ rescue StandardError => err
203
+ @errors.push(err)
204
+ next
205
+ end
206
+ f.puts("## #{path}")
207
+ f.puts(content)
208
+ f.puts("")
209
+ end
210
+ end
211
+ end
212
+
213
+ def save_irq_info()
214
+ File.open("#{@output_dir}/irq-smp-affinity.log", "w") do |f|
215
+ Dir.glob('/proc/irq/*/smp_affinity').sort_by do |path|
216
+ irqno = File.basename(File.dirname(path)).to_i
217
+ end.each do |path|
218
+ f.puts("## cat #{path}")
219
+ f.puts(`cat #{path}`)
220
+ f.puts("")
221
+ end
222
+ end
223
+ end
224
+
225
+ def save_device_info()
226
+ (Dir.glob('/sys/block/sd*') +
227
+ Dir.glob('/sys/block/xvd*')).each do |sd_dev|
228
+ File.open("#{@output_dir}/block-#{File.basename(sd_dev)}.log", "w") do |f|
229
+ f.puts("## ls -l #{sd_dev}")
230
+ f.puts(`ls -l #{sd_dev}`)
231
+ f.puts("")
232
+ ['device/queue_depth',
233
+ 'device/queue_type',
234
+ 'device/iorequest_cnt',
235
+ 'device/vendor',
236
+ 'queue/scheduler',
237
+ 'queue/nr_requests',
238
+ 'queue/rq_affinity',
239
+ 'queue/nomerges',
240
+ 'queue/add_random',
241
+ 'queue/rotational',
242
+ 'queue/max_hw_sectors_kb',
243
+ 'queue/physical_block_size',
244
+ 'queue/optimal_io_size',
245
+ ].each do |entity|
246
+ path = "#{sd_dev}/#{entity}"
247
+ if File.exists?(path)
248
+ f.puts("## #{path}")
249
+ f.puts(`cat #{path}`)
250
+ f.puts("")
251
+ end
252
+ end
253
+ end
254
+ end
255
+ end
256
+
257
+ def save_disk_info()
258
+ File.open("#{@output_dir}/disk-by-path.log", "w") do |f|
259
+ f.puts(`ls -l /dev/disk/by-path/`)
260
+ end
261
+
262
+ File.open("#{@output_dir}/disk-by-uuid.log", "w") do |f|
263
+ f.puts(`ls -l /dev/disk/by-uuid/`)
264
+ end
265
+
266
+ File.open("#{@output_dir}/disk-by-id.log", "w") do |f|
267
+ f.puts(`ls -l /dev/disk/by-id/`)
268
+ end
269
+
270
+ File.open("#{@output_dir}/disk-multipath.log", "w") do |f|
271
+ f.puts(`/sbin/multipath -ll 2>&1`)
272
+ end
273
+ end
274
+
275
+ def save_pci_info()
276
+ lspci_bin = find_executable("lspci")
277
+
278
+ if lspci_bin
279
+ File.open("#{@output_dir}/lspci.log", "w") do |f|
280
+ f.puts(`#{lspci_bin} -D -vvv`)
281
+ end
282
+ end
283
+
284
+ Dir.glob("/sys/devices/pci*/*/*/vendor") do |vendor|
285
+ pcidir = File.dirname(vendor)
286
+
287
+ prefix = [File.basename(File.dirname(File.dirname(pcidir))),
288
+ File.basename(File.dirname(pcidir)),
289
+ File.basename(pcidir)].join("-")
290
+
291
+ File.open("#{@output_dir}/#{prefix}.log", "w") do |f|
292
+ f.puts("## ls -l #{pcidir}")
293
+ f.puts(`ls -l #{pcidir}`)
294
+ f.puts("")
295
+ Dir.entries(pcidir).select do |filename|
296
+ ! (["remove", "reset", "rescan", "rom", "uevent", "config",
297
+ "vpd"
298
+ ].include?(filename) ||
299
+ filename =~ /\Aresource\d+\Z/ ||
300
+ filename =~ /\Aresource\d+_wc\Z/ # DDN device specific node (?)
301
+ )
302
+ end.each do |filename|
303
+ path = File.expand_path(filename, pcidir)
304
+ next unless File.file?(path)
305
+ content = read_file(path)
306
+ if content
307
+ f.puts("## #{path}")
308
+ f.puts(content)
309
+ f.puts("")
310
+ end
311
+ end
312
+
313
+ msi_irqs_dir = File.expand_path("msi_irqs", pcidir)
314
+ if File.directory?(msi_irqs_dir)
315
+ f.puts("## ls -l #{msi_irqs_dir}")
316
+ f.puts(`ls -l #{msi_irqs_dir}`)
317
+ f.puts("")
318
+
319
+ Dir.glob("#{msi_irqs_dir}/*/mode").each do |mode_path|
320
+ content = read_file(mode_path)
321
+ f.puts("## #{mode_path}")
322
+ f.puts(content)
323
+ f.puts("")
324
+ end
325
+ end
326
+ end
327
+ end
328
+ end
329
+
330
+ def save_module_info()
331
+ modules = []
332
+
333
+ if lsmod_bin = find_executable("lsmod")
334
+ File.open("#{@output_dir}/lsmod.log", "w") do |f|
335
+ content = `#{lsmod_bin}`
336
+ f.puts(content)
337
+
338
+ lines = content.split("\n")
339
+ lines.shift # omit 1st line (label)
340
+ modules = lines.map do |line|
341
+ line.split[0]
342
+ end
343
+ end
344
+ else
345
+ return
346
+ end
347
+
348
+ modinfo_bin = find_executable("modinfo")
349
+
350
+ Dir.glob("/sys/module/*/parameters") do |params_dir|
351
+ module_name = File.basename(File.dirname(params_dir))
352
+ next unless modules.include?(module_name)
353
+
354
+ File.open("#{@output_dir}/module-#{module_name}.log", "w") do |f|
355
+ Dir.glob("#{params_dir}/*").each do |param_file|
356
+ param_name = File.basename(param_file)
357
+ # blacklisting
358
+ next if module_name == "apparmor" && param_name == "audit"
359
+ next if module_name == "apparmor" && param_name == "mode"
360
+
361
+ content = read_file(param_file)
362
+ f.puts("## #{param_file}")
363
+ f.puts(content)
364
+ f.puts("")
365
+ end
366
+
367
+ if modinfo_bin
368
+ content = `#{modinfo_bin} #{module_name}`
369
+ f.puts("## modinfo #{module_name}")
370
+ f.puts(content)
371
+ f.puts("")
372
+ end
373
+ end
374
+ end
375
+ end
376
+
377
+ def save_distro_info()
378
+ File.open("#{@output_dir}/distro.log", "w") do |f|
379
+ if system("which uname >/dev/null 2>&1")
380
+ content = `uname -a`
381
+ f.puts("## uname -a")
382
+ f.puts(content)
383
+ f.puts("")
384
+ end
385
+
386
+ if system("which lsb_release >/dev/null 2>&1")
387
+ content = `lsb_release -a 2>/dev/null`
388
+ f.puts("## lsb_release -a")
389
+ f.puts(content)
390
+ f.puts("")
391
+ end
392
+
393
+ if File.exists?("/etc/debian_version")
394
+ content = read_file("/etc/debian_version")
395
+ f.puts("## /etc/debian_version")
396
+ f.puts(content)
397
+ f.puts("")
398
+ end
399
+
400
+ if File.exists?("/etc/redhat-release")
401
+ content = read_file("/etc/redhat-release")
402
+ f.puts("## /etc/redhat-release")
403
+ f.puts(content)
404
+ f.puts("")
405
+ end
406
+ end
407
+ end
408
+
409
+ def save_sysctl_info()
410
+ sysctl_bin = find_executable("sysctl")
411
+
412
+ if sysctl_bin
413
+ File.open("#{@output_dir}/sysctl.log", "w") do |f|
414
+ content = `#{sysctl_bin} -a`
415
+ f.puts("## sysctl -a")
416
+ f.puts(content)
417
+ f.puts("")
418
+ end
419
+ end
420
+ end
421
+
422
+ def save_dmidecode_info()
423
+ dmidecode_bin = find_executable("dmidecode")
424
+
425
+ if dmidecode_bin
426
+ File.open("#{@output_dir}/dmidecode.log", "w") do |f|
427
+ content = `#{dmidecode_bin} 2>&1`
428
+ f.puts("## dmidecode")
429
+ f.puts(content)
430
+ f.puts("")
431
+ end
432
+ end
433
+ end
434
+
435
+ def save_megaraid_info(megacli_bin)
436
+ File.open("#{@output_dir}/megaraid.log", "w") do |f|
437
+ params_list = ["-AdpCount",
438
+ "-AdpAllinfo -aALL",
439
+ "-AdpBbuCmd -aALL",
440
+ "-LDInfo -Lall -aALL",
441
+ "-PDList -aALL"
442
+ ].each do |params|
443
+ f.puts("## #{megacli_bin} #{params}")
444
+ f.puts(`#{megacli_bin} #{params}`.gsub(/\r/, ""))
445
+ f.puts("")
446
+ end
447
+ end
448
+ end
449
+ end
450
+
451
+ end # module Command
452
+ end # module PerfMonger
453
+