class Object

Constants

Y_DEFAULT

Public Instance Methods

analyze_header_create_plot_title(prefix, smooth, inversion, csv_header) click to toggle source
# File lib/dstat_plot.rb, line 105
def analyze_header_create_plot_title(prefix, smooth, inversion, csv_header)
  plot_title = "#{prefix} over time"
  if smooth then plot_title += " (smoothing: #{smooth})" end
  if csv_header[2].index("Host:")
    plot_title += '\n' + "(Host: #{csv_header[2][1]} User: #{csv_header[2][6]} Date: #{csv_header[3].last})"
  end
  if inversion then plot_title += '\n(inverted)' end
  plot_title
end
average(data, slice_size) click to toggle source

Calculate the average of groups of values from data Params:

+data

Array containing the data

+slice_size

number of values each group of data should contain

# File lib/dstat_plot.rb, line 61
def average(data, slice_size)
  reduced_data = []
  data.each_slice(slice_size) do |slice|
      reduced_data.push(slice.reduce(:+) / slice.size)
  end
  reduced_data
end
create_gnuplot_dataset(timecode, values, no_plot_key, smooth, file) click to toggle source

Create the GnuplotDataSet that is going to be printed. Params:

+timecode

Array containing the timestamps

+values

Array containing the actual values

+no_plot_key

boolean to de-/activate plotkey

+smooth

nil or smoothing algorithm

+file

file

# File lib/dstat_plot.rb, line 93
def create_gnuplot_dataset(timecode, values, no_plot_key, smooth, file)
  Gnuplot::DataSet.new([timecode, values]) do |gp_dataset|
    gp_dataset.with = "lines"
    if no_plot_key then
      gp_dataset.notitle
    else
      gp_dataset.title = (File.basename file).gsub('_', '\\_')
    end
    gp_dataset.smooth = smooth unless smooth.nil?
  end
end
data_preprocessing(dataset_container, slice_size) click to toggle source

Preprocesses the data contained in all datasets in the dataset_container Groups of values are averaged with respect to timecode and actual data Params:

+dataset_container

Hash that holds the datasets and further information

+slice_size

size of the group that averages are supposed to be calculated from

# File lib/dstat_plot.rb, line 74
def data_preprocessing(dataset_container, slice_size)
  dataset_container[:datasets].each do |dataset|
    timecode = dataset.data[0]
    reduced_timecode = average(timecode, slice_size)

    values = dataset.data[1].map { |value| value.to_f }
    reduced_values = average(values, slice_size)

    dataset.data = [reduced_timecode, reduced_values]
  end
end
generate_filename(output, column, category, field, target_dir) click to toggle source
# File lib/dstat_plot.rb, line 37
def generate_filename(output, column, category, field, target_dir)
  if output.nil? || File.directory?(output) # if an output file is not explicitly stated or if it's a directory
    # generate filename
    if column
      generated_filename = "dstat-column#{column}.png"
    else
      generated_filename = "#{category}-#{field}.png".sub("/", "_")
    end
    
    # add directory portion
    if output.nil?
      filename = File.join(target_dir, generated_filename)
    elsif File.directory?(output)
      filename = File.join(output, generated_filename)
    end
  else # specific path+file is given so just use that
    filename = output
  end
end
plot(dataset_container, category, field, dry, filename) click to toggle source
# File lib/dstat_plot.rb, line 15
def plot(dataset_container, category, field, dry, filename)
  Gnuplot.open do |gp|
    Gnuplot::Plot.new(gp) do |plot|
      plot.title dataset_container[:plot_title].gsub('_', '\\\\\\\\_')
      plot.xlabel "Time in seconds"
      plot.ylabel "#{category}: #{field}"
      plot.yrange "[0:#{dataset_container[:y_max] * 1.05}]"
      if dataset_container[:autoscale] then plot.set "autoscale" end
      plot.key "out vert right top"

      unless dry
        format = filename.split('.')[-1]
        plot.terminal format + ' size 1600,800 enhanced font "Helvetica,11"'
        plot.output filename
        puts "Saving plot to '#{filename}'"
      end

      plot.data = dataset_container[:datasets]
    end
  end
end
read_data_from_csv(files, category, field, column, no_plot_key, y_max, inversion, title, smooth) click to toggle source

returns the values from a headerless csv file

# File lib/dstat_plot.rb, line 135
def read_data_from_csv(files, category, field, column, no_plot_key, y_max, inversion, title, smooth)
  plot_title = nil
  datasets = []
  autoscale = false
  overall_max = y_max.nil? ? Y_DEFAULT : y_max

  files.each do |file|
    csv = CSV.read(file)

    if column
      if $verbose then puts "Reading from csv to get column #{column}." end
      prefix = "dstat-column #{column}"
    else
      if $verbose then puts "Reading from csv to get #{category}-#{field}." end
      column = translate_to_column(category, field, csv)
      prefix = "#{category}-#{field}"
    end
    
    if plot_title.nil? # this only needs to be done for the first file
      if title
        plot_title = title
      else
        plot_title = analyze_header_create_plot_title(prefix, smooth, inversion != 0.0, csv[0..6])
      end
    end

    if csv[2].index "Host:"
      csv = csv.drop(7)
    end

    begin
      csv = csv.transpose
    rescue IndexError => e
      puts 'ERROR: It appears that your csv file is malformed. Check for incomplete lines, empty lines etc.'
      puts e.backtrace[0] + e.message
      exit
    end

    timecode = csv[0].map { |timestamp| timestamp.to_f - csv[0].first.to_f }

    values = csv[column]
    if inversion != 0.0
      values.map! { |value| (value.to_f - inversion).abs }
      overall_max = inversion
    end
    
    if y_max.nil?
      local_maximum = values.max { |a, b| a.to_f <=> b.to_f }.to_f
      if local_maximum > overall_max then overall_max = local_maximum end
    end

    dataset = create_gnuplot_dataset(timecode, values, no_plot_key, smooth, file)
    datasets.push dataset
  end

  if $verbose then puts "datasets: #{datasets.count} \nplot_title: #{plot_title} \ny_max: #{y_max} \nautoscale: #{autoscale}" end
  dataset_container = { :datasets => datasets, :plot_title => plot_title, :y_max => overall_max, :autoscale => autoscale }
end
read_options_and_arguments() click to toggle source
# File lib/dstat_plot.rb, line 194
def read_options_and_arguments
  opts = {} # Hash that holds all the options

  optparse = OptionParser.new do |parser|
    # banner that is displayed at the top
    parser.banner = "Usage: \b
    dstat_plot.rb [options] -c CATEGORY -f FIELD [directory | file1 file2 ...] or \b 
    dstat_plot.rb [options] -l COLUMN [directory | file1 file2 ...]\n\n"

    ### options and what they do
    parser.on('-v', '--verbose', 'Output more information') do
      $verbose = true
    end

    opts[:inversion] = 0.0
    parser.on('-i', '--invert [VALUE]', Float, 'Invert the graph such that inverted(x) = VALUE - f(x),', 'default is 100.') do |value|
      opts[:inversion] = value.nil? ? 100.0 : value
    end

    opts[:no_plot_key] = false
    parser.on('-n', '--no-key', 'No plot key is printed.') do
      opts[:no_plot_key] = true
    end

    opts[:dry] = false
    parser.on('-d', '--dry', 'Dry run. Plot is not saved to file but instead displayed with gnuplot.') do
      opts[:dry] = true
    end

    opts[:output] = nil
    parser.on('-o','--output FILE|DIR', 'File or Directory that plot should be saved to. ' \
      'If a directory is given', 'the filename will be generated. Default is csv file directory.') do |path|
      opts[:output] = path
    end

    opts[:y_max]
    parser.on('-y', '--y-range RANGE', Float, 'Sets the y-axis range. Default is 105. ' \
      'If a value exceeds this range,', '"autoscale" is enabled.') do |range|
      opts[:y_max] = range      
    end

    opts[:title] = nil
    parser.on('-t', '--title TITLE', 'Override the default title of the plot.') do |title|
      opts[:title] = title
    end

    opts[:smooth] = nil
    parser.on('-s', '--smoothing ALGORITHM', 'Smoothes the graph using the given algorithm.') do |algorithm|
        algorithms = [ 'unique', 'frequency', 'cumulative', 'cnormal', 'kdensity', 'unwrap',
          'csplines', 'acsplines', 'mcsplines', 'bezier', 'sbezier' ]
        if algorithms.index(algorithm)
          opts[:smooth] = algorithm
        else
          puts "#{algorithm} is not a valid option as an algorithm."
          exit
        end
    end

    opts[:slice_size] = nil
    parser.on('-a', '--average-over SLICE_SIZE', Integer, 'Calculates the everage for slice_size large groups of values.',"\n") do |slice_size|
      opts[:slice_size] = slice_size
    end

    opts[:category] = nil
    parser.on('-c', '--category CATEGORY', 'Select the category.') do |category|
      opts[:category] = category
    end

    opts[:field] = nil
    parser.on('-f', '--field FIELD' , 'Select the field.') do |field|
      opts[:field] = field
    end

    opts[:column] = nil
    parser.on('-l', '--column COLUMN', 'Select the desired column directly.', "\n") do |column|
      unless opts[:category] && opts[:field]  # -c and -f override -l
        opts[:column] = column.to_i
      end
    end

    # This displays the help screen
    parser.on_tail('-h', '--help', 'Display this screen.' ) do
      puts parser
      exit
    end
  end

  # there are two forms of the parse method. 'parse'
  # simply parses ARGV, while 'parse!' parses ARGV
  # and removes all options and parameters found. What's
  # left is the list of files
  optparse.parse!
  if $verbose then puts "opts: #{opts.inspect}" end

  if opts[:category].nil? || opts[:category].nil?
    if opts[:column].nil?
      puts "[Error] (-c CATEGORY and -f FIELD) or (-l COLUMN) are mandatory parameters.\n\n"
      puts optparse
      exit
    end
  end

  # if ARGV is empty at this point no directory or file(s) is specified
  # and the current working directory is used
  if ARGV.empty? then ARGV.push "." end

  files = []
  if File.directory?(ARGV.last) then
    opts[:target_dir] = ARGV.last.chomp("/") # cuts of "/" from the end if present
    files = Dir.glob "#{opts[:target_dir]}/*.csv"
    files = files.sort
  else
    opts[:target_dir] = File.dirname ARGV.first
    ARGV.each do |filename|
      files.push filename
    end
  end
  puts "Plotting data from #{files.count} file(s)."
  opts[:files] = files
  if $verbose then puts "files: #{files.count} #{files.inspect}" end

# opts = { :inversion, :no_plot_key, :dry, :output, :y_max, :title, :category, :field, :column, :target_dir, :files }
  opts
end
translate_to_column(category, field, csv) click to toggle source
# File lib/dstat_plot.rb, line 115
def translate_to_column(category, field, csv)
  category_index = csv[5].index category
  if category_index.nil?
    puts "'#{category}' is not a valid parameter for 'category'."
    puts "Allowed categories: #{csv[5].reject{ |elem| elem == nil }.inspect}"
    exit 0
  end

  field_index = csv[6].drop(category_index).index field
  if field_index.nil?
    puts "'#{field}' is not a valid parameter for 'field'."
    puts "Allowed fields: #{csv[6].reject{ |elem| elem == nil }.inspect}"
    exit 0
  end
  
  if $verbose then puts "'#{category}-#{field}' was translated to #{category_index + field_index}." end
  column = category_index + field_index
end