lib/perfmonger/command/plot.rb in perfmonger-0.8.2 vs lib/perfmonger/command/plot.rb in perfmonger-0.9.0

- old
+ new

@@ -23,10 +23,13 @@ @output_dir = Dir.pwd @output_type = 'pdf' @output_prefix = '' @save_gpfiles = false @disk_only_regex = nil + @disk_plot_read = true + @disk_plot_write = true + @disk_numkey_threshold = 10 end def parse_args(argv) @parser.on('--offset-time TIME') do |time| @offset_time = Float(time) @@ -75,10 +78,29 @@ @parser.on('--disk-only REGEX', "Select disk devices that matches REGEX") do |regex| @disk_only_regex = Regexp.compile(regex) end + @parser.on('--disk-read-only', "Plot only READ performance for disks") do + @disk_plot_read = true + @disk_plot_write = false + end + + @parser.on('--disk-write-only', "Plot only WRITE performance for disks") do + @disk_plot_read = false + @disk_plot_write = true + end + + @parser.on('--disk-read-write', "Plot READ and WRITE performance for disks") do + @disk_plot_read = true + @disk_plot_write = true + end + + @parser.on('--disk-numkey-threshold NUM', "Legends of per-disk plots are turned off if the number of disks is larger than this value.") do |num| + @disk_numkey_threshold = num.to_i + end + @parser.parse!(argv) if argv.size == 0 puts("ERROR: PerfMonger log file is required") puts(@parser.help) @@ -102,201 +124,235 @@ puts("ERROR: PerfMonger requires pdfcairo-supported gnuplot") puts(@parser.help) exit(false) end - player_bin = ::PerfMonger::Command::CoreFinder.player() + formatter_bin = ::PerfMonger::Command::CoreFinder.plot_formatter() - tmpfile = Tempfile.new("jsondata") - IO.popen([player_bin, @data_file], "r").each_line do |line| - tmpfile.print(line) + @tmpdir = Dir.mktmpdir + + @disk_dat = File.expand_path("disk.dat", @tmpdir) + @cpu_dat = File.expand_path("cpu.dat", @tmpdir) + + meta_json = nil + IO.popen([formatter_bin, "-perfmonger", @data_file, "-cpufile", @cpu_dat, "-diskfile", @disk_dat], "r") do |io| + meta_json = io.read end - tmpfile.flush() + if $?.exitstatus != 0 + puts("ERROR: failed to run perfmonger-plot-formatter") + exit(false) + end + meta = JSON.parse(meta_json) - plot_ioinfo(tmpfile.path) - plot_cpuinfo(tmpfile.path) + plot_disk(meta) + plot_cpu(meta) + + true end private - def plot_ioinfo(json_file) - iops_pdf_filename = @output_prefix + 'iops.pdf' - transfer_pdf_filename = @output_prefix + 'transfer.pdf' - gp_filename = @output_prefix + 'io.gp' - dat_filename = @output_prefix + 'io.dat' + def plot_disk(meta) + iops_pdf_filename = @output_prefix + 'disk-iops.pdf' + transfer_pdf_filename = @output_prefix + 'disk-transfer.pdf' + total_iops_pdf_filename = @output_prefix + 'disk-total-iops.pdf' + total_transfer_pdf_filename = @output_prefix + 'disk-total-transfer.pdf' + gp_filename = @output_prefix + 'disk.gp' + dat_filename = @output_prefix + 'disk.dat' if @output_type != 'pdf' - iops_img_filename = @output_prefix + 'iops.' + @output_type - transfer_img_filename = @output_prefix + 'transfer.' + @output_type + iops_img_filename = @output_prefix + 'disk-iops.' + @output_type + transfer_img_filename = @output_prefix + 'disk-transfer.' + @output_type + total_iops_img_filename = @output_prefix + 'disk-total-iops.' + @output_type + total_transfer_img_filename = @output_prefix + 'disk-total-transfer.' + @output_type else iops_img_filename = nil transfer_img_filename = nil + total_iops_img_filename = nil + total_transfer_img_filename = nil end - Dir.mktmpdir do |working_dir| - Dir.chdir(working_dir) do - datafile = File.open(dat_filename, 'w') - gpfile = File.new(gp_filename, 'w') + start_time = meta["start_time"] + end_time = meta["end_time"] - start_time = nil - devices = nil + Dir.chdir(@tmpdir) do + gpfile = File.new(gp_filename, 'w') - File.open(json_file).each_line do |line| - record = JSON.parse(line) - time = record["time"] - diskinfo = record["disk"] - return unless diskinfo + total_iops_plot_stmt_list = [] + iops_plot_stmt_list = meta["disk"]["devices"].map do |dev_entry| + devname = dev_entry["name"] + idx = dev_entry["idx"] - start_time ||= time - devices ||= diskinfo["devices"] + if devname == "total" + if @disk_plot_read + total_iops_plot_stmt_list.push("\"disk.dat\" ind #{idx} usi 1:2 with lines lw 2 title \"#{devname} read\"") + end + if @disk_plot_write + total_iops_plot_stmt_list.push("\"disk.dat\" ind #{idx} usi 1:3 with lines lw 2 title \"#{devname} write\"") + end - if @disk_only_regex - devices = devices.select do |devname| - devname =~ @disk_only_regex - end + [] + elsif @disk_only_regex && !(devname =~ @disk_only_regex) + [] + else + plot_stmt = [] + + if @disk_plot_read + plot_stmt.push("\"disk.dat\" ind #{idx} usi 1:2 with lines lw 2 title \"#{devname} read\"") end + if @disk_plot_write + plot_stmt.push("\"disk.dat\" ind #{idx} usi 1:3 with lines lw 2 title \"#{devname} write\"") + end - datafile.puts([time - start_time, - devices.map{|device| - [diskinfo[device]["riops"], diskinfo[device]["wiops"], - diskinfo[device]["rkbyteps"] * 512 / 1024 / 1024, # in MB/s - diskinfo[device]["wkbyteps"] * 512 / 1024 / 1024, # in MB/s - ] - }].flatten.map(&:to_s).join("\t")) + plot_stmt end + end.flatten - datafile.close + total_transfer_plot_stmt_list = [] + transfer_plot_stmt_list = meta["disk"]["devices"].map do |dev_entry| + devname = dev_entry["name"] + idx = dev_entry["idx"] - col_idx = 2 - iops_plot_stmt_list = devices.map do |device| - plot_stmt = [] - plot_stmt.push("\"#{dat_filename}\" usi 1:#{col_idx} with lines lw 2 title \"#{device} read\"") - plot_stmt.push("\"#{dat_filename}\" usi 1:#{col_idx + 1} with lines lw 2 title \"#{device} write\"") - col_idx += 4 - plot_stmt - end.flatten + if devname == "total" + if @disk_plot_read + total_transfer_plot_stmt_list.push("\"disk.dat\" ind #{idx} usi 1:4 with lines lw 2 title \"#{devname} read\"") + end + if @disk_plot_write + total_transfer_plot_stmt_list.push("\"disk.dat\" ind #{idx} usi 1:5 with lines lw 2 title \"#{devname} write\"") + end - col_idx = 4 - transfer_plot_stmt_list = devices.map do |device| + [] + elsif @disk_only_regex && !(devname =~ @disk_only_regex) + [] + else plot_stmt = [] - plot_stmt.push("\"#{dat_filename}\" usi 1:#{col_idx} with lines lw 2 title \"#{device} read\"") - plot_stmt.push("\"#{dat_filename}\" usi 1:#{col_idx + 1} with lines lw 2 title \"#{device} write\"") - col_idx += 4 + + if @disk_plot_read + plot_stmt.push("\"disk.dat\" ind #{idx} usi 1:4 with lines lw 2 title \"#{devname} read\"") + end + if @disk_plot_write + plot_stmt.push("\"disk.dat\" ind #{idx} usi 1:5 with lines lw 2 title \"#{devname} write\"") + end + plot_stmt - end.flatten + end + end.flatten - gpfile.puts <<EOS + if iops_plot_stmt_list.size == 0 + puts("No plot target disk devices.") + return + end + + num_dev = meta["disk"]["devices"].select do |dev_entry| + dev_entry["name"] != "total" + end.size + + if num_dev > @disk_numkey_threshold + set_key_stmt = "unset key" + else + set_key_stmt = "set key below center" + end + + gpfile.puts <<EOS set term pdfcairo enhanced color -set title "IOPS: #{File.basename(@data_file)}" +set title "IOPS" set size 1.0, 1.0 set output "#{iops_pdf_filename}" set xlabel "elapsed time [sec]" set ylabel "IOPS" set grid -set xrange [#{@offset_time}:*] +set xrange [#{@offset_time}:#{end_time - start_time}] set yrange [0:*] -set key below center - +#{set_key_stmt} plot #{iops_plot_stmt_list.join(",\\\n ")} +set title "Total IOPS" +unset key +set output "#{total_iops_pdf_filename}" +plot #{total_iops_plot_stmt_list.join(",\\\n ")} -set title "Transfer rate: #{File.basename(@data_file)}" + +set title "Transfer rate" set output "#{transfer_pdf_filename}" set ylabel "transfer rate [MB/s]" +#{set_key_stmt} plot #{transfer_plot_stmt_list.join(",\\\n ")} + +set title "Total transfer rate" +set output "#{total_transfer_pdf_filename}" +unset key +plot #{total_transfer_plot_stmt_list.join(",\\\n ")} EOS + gpfile.close - gpfile.close + system("gnuplot #{gpfile.path}") - system("gnuplot #{gpfile.path}") + if @output_type != 'pdf' + system("convert -density 150 -background white #{iops_pdf_filename} #{iops_img_filename}") + system("convert -density 150 -background white #{transfer_pdf_filename} #{transfer_img_filename}") + system("convert -density 150 -background white #{total_iops_pdf_filename} #{total_iops_img_filename}") + system("convert -density 150 -background white #{total_transfer_pdf_filename} #{total_transfer_img_filename}") + end - if @output_type != 'pdf' - system("convert -density 150 -background white #{iops_pdf_filename} #{iops_img_filename}") - system("convert -density 150 -background white #{transfer_pdf_filename} #{transfer_img_filename}") - end + end # chdir - end # chdir + copy_targets = [] + copy_targets += [iops_pdf_filename, transfer_pdf_filename] + copy_targets += [total_iops_pdf_filename, total_transfer_pdf_filename] + copy_targets.push(iops_img_filename) if iops_img_filename + copy_targets.push(transfer_img_filename) if transfer_img_filename + copy_targets.push(total_iops_img_filename) if total_iops_img_filename + copy_targets.push(total_transfer_img_filename) if total_transfer_img_filename - copy_targets = [iops_pdf_filename, transfer_pdf_filename] - copy_targets.push(iops_img_filename) if iops_img_filename - copy_targets.push(transfer_img_filename) if transfer_img_filename + if @save_gpfiles + copy_targets.push(dat_filename) + copy_targets.push(gp_filename) + end - if @save_gpfiles - copy_targets.push(dat_filename) - copy_targets.push(gp_filename) - end - - copy_targets.each do |target| - FileUtils.copy(File.join(working_dir, target), @output_dir) - end - end # mktempdir + copy_targets.each do |target| + FileUtils.copy(File.join(@tmpdir, target), @output_dir) + end end # def - def plot_cpuinfo(json_file) + def plot_cpu(meta) pdf_filename = @output_prefix + 'cpu.pdf' gp_filename = @output_prefix + 'cpu.gp' dat_filename = @output_prefix + 'cpu.dat' all_pdf_filename = @output_prefix + 'allcpu.pdf' all_gp_filename = @output_prefix + 'allcpu.gp' - all_dat_filename = @output_prefix + 'allcpu.dat' if @output_type != 'pdf' img_filename = @output_prefix + 'cpu.' + @output_type all_img_filename = @output_prefix + 'allcpu.' + @output_type else img_filename = nil all_img_filename = nil end - Dir.mktmpdir do |working_dir| - Dir.chdir(working_dir) do - datafile = File.open(dat_filename, 'w') - gpfile = File.open(gp_filename, 'w') - all_datafile = File.open(all_dat_filename, 'w') - all_gpfile = File.open(all_gp_filename, 'w') + start_time = meta["start_time"] + end_time = meta["end_time"] - start_time = nil - end_time = 0 - devices = nil - nr_cpu = nil + Dir.chdir(@tmpdir) do + gpfile = File.open(gp_filename, 'w') + all_gpfile = File.open(all_gp_filename, 'w') - File.open(json_file).each_line do |line| - record = JSON.parse(line) + devices = nil + nr_cpu = meta["cpu"]["num_core"] - time = record["time"] - cpuinfo = record["cpu"] - return unless cpuinfo - nr_cpu = cpuinfo['num_core'] + plot_stmt_list = [] + %w|%usr %nice %sys %iowait %hardirq %softirq %steal %guest|.each_with_index do |key, idx| + stack_columns = (0..idx).to_a.map{|x| x + 2} + plot_stmt = "\"cpu.dat\" ind 0 usi 1:(#{stack_columns.map{|i| "$#{i}"}.join("+")}) with filledcurve x1 lw 0 lc #{idx+1} title \"#{key}\"" + plot_stmt_list << plot_stmt + end - cores = cpuinfo['cores'] - - start_time ||= time - end_time = [end_time, time].max - - datafile.puts([time - start_time, - %w|usr nice sys iowait hardirq softirq steal guest idle|.map do |key| - cores.map{|core| core[key]}.inject(&:+) - end].flatten.map(&:to_s).join("\t")) - end - datafile.close - - col_idx = 2 - columns = [] - plot_stmt_list = [] - %w|%usr %nice %sys %iowait %hardirq %softirq %steal %guest|.each do |key| - columns << col_idx - plot_stmt = "\"#{datafile.path}\" usi 1:(#{columns.map{|i| "$#{i}"}.join("+")}) with filledcurve x1 lw 0 lc #{col_idx - 1} title \"#{key}\"" - plot_stmt_list << plot_stmt - col_idx += 1 - end - - pdf_file = File.join(@output_dir, "cpu.pdf") - gpfile.puts <<EOS + pdf_file = File.join(@output_dir, "cpu.pdf") + gpfile.puts <<EOS set term pdfcairo enhanced color -set title "CPU usage: #{File.basename(@data_file)} (max: #{nr_cpu*100}%)" +set title "CPU usage (max: #{nr_cpu*100}%)" set output "#{pdf_filename}" set key outside center bottom horizontal set size 1.0, 1.0 set xlabel "elapsed time [sec]" @@ -307,59 +363,56 @@ set yrange [0:*] plot #{plot_stmt_list.reverse.join(",\\\n ")} EOS - gpfile.close - system("gnuplot #{gpfile.path}") + gpfile.close + system("gnuplot #{gpfile.path}") - if @output_type != 'pdf' - system("convert -density 150 -background white #{pdf_filename} #{img_filename}") - end + if @output_type != 'pdf' + system("convert -density 150 -background white #{pdf_filename} #{img_filename}") + end - ## Plot all CPUs in a single file + ## Plot all CPUs in a single file - nr_cpu_factors = factors(nr_cpu) - nr_cols = nr_cpu_factors.select do |x| - x <= Math.sqrt(nr_cpu) - end.max - nr_cols ||= Math.sqrt(nr_cpu).ceil - nr_rows = nr_cpu / nr_cols + nr_cpu_factors = factors(nr_cpu) + nr_cols = nr_cpu_factors.select do |x| + x <= Math.sqrt(nr_cpu) + end.max + nr_cols ||= Math.sqrt(nr_cpu).ceil + nr_rows = nr_cpu / nr_cols - all_gpfile.puts <<EOS -set term pdfcairo color enhanced size 8.5inch, 11inch + plot_height = 8 + + if nr_rows == 1 + plot_height /= 2.0 + end + + all_gpfile.puts <<EOS +set term pdfcairo color enhanced size 8.5inch, #{plot_height}inch set output "#{all_pdf_filename}" set size 1.0, 1.0 set multiplot set grid set xrange [#{@offset_time}:#{end_time - start_time}] set yrange [0:101] EOS - legend_height = 0.04 - nr_cpu.times do |cpu_idx| - all_datafile.puts("# cpu #{cpu_idx}") - File.open(json_file).each_line do |line| - record = JSON.parse(line) - time = record["time"] - cpurec = record["cpu"]["cores"][cpu_idx] - all_datafile.puts([time - start_time, - cpurec["usr"] + cpurec["nice"], - cpurec["sys"], - cpurec["hardirq"], - cpurec["softirq"], - cpurec["steal"] + cpurec["guest"], - cpurec["iowait"]].map(&:to_s).join("\t")) - end - all_datafile.puts("") - all_datafile.puts("") + legend_height = 0.04 + nr_cpu.times do |cpu_idx| + xpos = (1.0 / nr_cols) * (cpu_idx % nr_cols) + ypos = ((1.0 - legend_height) / nr_rows) * (nr_rows - 1 - (cpu_idx / nr_cols).to_i) + legend_height - xpos = (1.0 / nr_cols) * (cpu_idx % nr_cols) - ypos = ((1.0 - legend_height) / nr_rows) * (nr_rows - 1 - (cpu_idx / nr_cols).to_i) + legend_height + plot_stmt_list = [] + %w|%usr %nice %sys %iowait %hardirq %softirq %steal %guest|.each_with_index do |key, idx| + stack_columns = (0..idx).to_a.map{|x| x + 2} + plot_stmt = "\"cpu.dat\" ind #{cpu_idx+1} usi 1:(#{stack_columns.map{|i| "$#{i}"}.join("+")}) with filledcurve x1 lw 0 lc #{idx+1} title \"#{key}\"" + plot_stmt_list << plot_stmt + end - all_gpfile.puts <<EOS + all_gpfile.puts <<EOS set title 'cpu #{cpu_idx}' offset 0.0,-0.7 font 'Arial,16' unset key set origin #{xpos}, #{ypos} set size #{1.0/nr_cols}, #{(1.0 - legend_height)/nr_rows} set rmargin 0.5 @@ -367,24 +420,24 @@ set tmargin 1.3 set bmargin 1.3 set xtics offset 0.0,0.5 set ytics offset 0.5,0 set style fill noborder -plot '#{all_datafile.path}' index #{cpu_idx} using 1:($2+$3+$4+$5+$6+$7) with filledcurve x1 lw 0 lc 6 title '%iowait', \\ - '#{all_datafile.path}' index #{cpu_idx} using 1:($2+$3+$4+$5+$6) with filledcurve x1 lw 0 lc 5 title '%other', \\ - '#{all_datafile.path}' index #{cpu_idx} using 1:($2+$3+$4+$5) with filledcurve x1 lw 0 lc 4 title '%soft', \\ - '#{all_datafile.path}' index #{cpu_idx} using 1:($2+$3+$4) with filledcurve x1 lw 0 lc 3 title '%irq', \\ - '#{all_datafile.path}' index #{cpu_idx} using 1:($2+$3) with filledcurve x1 lw 0 lc 2 title '%sys', \\ - '#{all_datafile.path}' index #{cpu_idx} using 1:2 with filledcurve x1 lw 0 lc 1 title '%usr' +plot #{plot_stmt_list.reverse.join(",\\\n ")} EOS + end # times - end - - all_gpfile.puts <<EOS + # plot legends + plot_stmt_list = [] + %w|%usr %nice %sys %iowait %hardirq %softirq %steal %guest|.each_with_index do |key, idx| + plot_stmt = "-1 with filledcurve x1 lw 0 lc #{idx+1} title \"#{key}\"" + plot_stmt_list << plot_stmt + end + all_gpfile.puts <<EOS unset title -set key center center horizontal font "Arial,16" +set key center center horizontal font "Arial,14" set origin 0.0, 0.0 set size 1.0, #{legend_height} set rmargin 0 set lmargin 0 set tmargin 0 @@ -392,48 +445,41 @@ unset tics set border 0 set yrange [0:1] # plot -1 with filledcurve x1 title '%usr' -plot -1 with filledcurve x1 lw 0 lc 1 title '%usr', \\ - -1 with filledcurve x1 lw 0 lc 2 title '%sys', \\ - -1 with filledcurve x1 lw 0 lc 3 title '%irq', \\ - -1 with filledcurve x1 lw 0 lc 4 title '%soft', \\ - -1 with filledcurve x1 lw 0 lc 5 title '%other', \\ - -1 with filledcurve x1 lw 0 lc 6 title '%iowait' +set xlabel "elapsed time [sec]" +plot #{plot_stmt_list.reverse.join(",\\\n ")} + EOS - all_datafile.fsync - all_gpfile.fsync - all_datafile.close - all_gpfile.close + all_gpfile.fsync + all_gpfile.close - system("gnuplot #{all_gpfile.path}") + system("gnuplot #{all_gpfile.path}") - if @output_type != 'pdf' - system("convert -density 150 -background white #{all_pdf_filename} #{all_img_filename}") - end + if @output_type != 'pdf' + system("convert -density 150 -background white #{all_pdf_filename} #{all_img_filename}") + end - end # chdir + end # chdir - copy_targets = [] + copy_targets = [] - copy_targets << pdf_filename - copy_targets << img_filename if img_filename - copy_targets << all_pdf_filename - copy_targets << all_img_filename if all_img_filename + copy_targets << pdf_filename + copy_targets << img_filename if img_filename + copy_targets << all_pdf_filename + copy_targets << all_img_filename if all_img_filename - if @save_gpfiles - copy_targets << gp_filename - copy_targets << dat_filename - copy_targets << all_gp_filename - copy_targets << all_dat_filename - end + if @save_gpfiles + copy_targets << gp_filename + copy_targets << dat_filename + copy_targets << all_gp_filename + end - copy_targets.each do |target| - FileUtils.copy(File.join(working_dir, target), @output_dir) - end - end # mktempdir + copy_targets.each do |target| + FileUtils.copy(File.join(@tmpdir, target), @output_dir) + end end # def private def factors(n) (2..([n, n / 2].max).to_i).select do |x|