perfmonger 0.6.1

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