diff --git a/share/install_gems/CentOS7/Gemfile.lock b/share/install_gems/CentOS7/Gemfile.lock index fe8fc4aeb1..c8cbdf50c8 100644 --- a/share/install_gems/CentOS7/Gemfile.lock +++ b/share/install_gems/CentOS7/Gemfile.lock @@ -58,6 +58,7 @@ GEM ffi-rzmq-core (>= 1.0.7) ffi-rzmq-core (1.0.7) ffi + gnuplot (2.6.2) hashie (3.6.0) highline (1.7.10) http-cookie (1.0.3) diff --git a/share/install_gems/CentOS8/Gemfile.lock b/share/install_gems/CentOS8/Gemfile.lock index 216c991b64..ce8a88e45d 100644 --- a/share/install_gems/CentOS8/Gemfile.lock +++ b/share/install_gems/CentOS8/Gemfile.lock @@ -65,6 +65,7 @@ GEM ffi-rzmq-core (>= 1.0.7) ffi-rzmq-core (1.0.7) ffi + gnuplot (2.6.2) hashie (3.6.0) highline (1.7.10) http-cookie (1.0.3) diff --git a/share/install_gems/Debian10/Gemfile.lock b/share/install_gems/Debian10/Gemfile.lock index 216c991b64..ce8a88e45d 100644 --- a/share/install_gems/Debian10/Gemfile.lock +++ b/share/install_gems/Debian10/Gemfile.lock @@ -65,6 +65,7 @@ GEM ffi-rzmq-core (>= 1.0.7) ffi-rzmq-core (1.0.7) ffi + gnuplot (2.6.2) hashie (3.6.0) highline (1.7.10) http-cookie (1.0.3) diff --git a/share/install_gems/Debian9/Gemfile.lock b/share/install_gems/Debian9/Gemfile.lock index 572dea27c2..6f3768e2f7 100644 --- a/share/install_gems/Debian9/Gemfile.lock +++ b/share/install_gems/Debian9/Gemfile.lock @@ -58,6 +58,7 @@ GEM ffi-rzmq-core (>= 1.0.7) ffi-rzmq-core (1.0.7) ffi + gnuplot (2.6.2) hashie (3.6.0) highline (1.7.10) http-cookie (1.0.3) diff --git a/share/install_gems/Fedora31/Gemfile.lock b/share/install_gems/Fedora31/Gemfile.lock index 5be039431e..e77a7c9bea 100644 --- a/share/install_gems/Fedora31/Gemfile.lock +++ b/share/install_gems/Fedora31/Gemfile.lock @@ -65,6 +65,7 @@ GEM ffi-rzmq-core (>= 1.0.7) ffi-rzmq-core (1.0.7) ffi + gnuplot (2.6.2) hashie (3.6.0) highline (1.7.10) http-cookie (1.0.3) diff --git a/share/install_gems/Fedora32/Gemfile.lock b/share/install_gems/Fedora32/Gemfile.lock index 5be039431e..e77a7c9bea 100644 --- a/share/install_gems/Fedora32/Gemfile.lock +++ b/share/install_gems/Fedora32/Gemfile.lock @@ -65,6 +65,7 @@ GEM ffi-rzmq-core (>= 1.0.7) ffi-rzmq-core (1.0.7) ffi + gnuplot (2.6.2) hashie (3.6.0) highline (1.7.10) http-cookie (1.0.3) diff --git a/share/install_gems/Gemfile b/share/install_gems/Gemfile index 21224291b1..1615534038 100644 --- a/share/install_gems/Gemfile +++ b/share/install_gems/Gemfile @@ -87,3 +87,4 @@ gem 'i18n', '~> 0.9' # packethost gem 'ffi-rzmq', '~> 2.0.7' # onehem (hooks) gem 'ipaddress', '~> 0.8.3' # sunstone, oneflow gem 'augeas', '~> 0.6' # serversync +gem 'gnuplot' # monitoring host plot diff --git a/share/install_gems/Ubuntu1604/Gemfile.lock b/share/install_gems/Ubuntu1604/Gemfile.lock index 572dea27c2..6f3768e2f7 100644 --- a/share/install_gems/Ubuntu1604/Gemfile.lock +++ b/share/install_gems/Ubuntu1604/Gemfile.lock @@ -58,6 +58,7 @@ GEM ffi-rzmq-core (>= 1.0.7) ffi-rzmq-core (1.0.7) ffi + gnuplot (2.6.2) hashie (3.6.0) highline (1.7.10) http-cookie (1.0.3) diff --git a/share/install_gems/Ubuntu1804/Gemfile.lock b/share/install_gems/Ubuntu1804/Gemfile.lock index 216c991b64..ce8a88e45d 100644 --- a/share/install_gems/Ubuntu1804/Gemfile.lock +++ b/share/install_gems/Ubuntu1804/Gemfile.lock @@ -65,6 +65,7 @@ GEM ffi-rzmq-core (>= 1.0.7) ffi-rzmq-core (1.0.7) ffi + gnuplot (2.6.2) hashie (3.6.0) highline (1.7.10) http-cookie (1.0.3) diff --git a/share/install_gems/Ubuntu1910/Gemfile.lock b/share/install_gems/Ubuntu1910/Gemfile.lock index 216c991b64..ce8a88e45d 100644 --- a/share/install_gems/Ubuntu1910/Gemfile.lock +++ b/share/install_gems/Ubuntu1910/Gemfile.lock @@ -65,6 +65,7 @@ GEM ffi-rzmq-core (>= 1.0.7) ffi-rzmq-core (1.0.7) ffi + gnuplot (2.6.2) hashie (3.6.0) highline (1.7.10) http-cookie (1.0.3) diff --git a/share/install_gems/Ubuntu2004/Gemfile.lock b/share/install_gems/Ubuntu2004/Gemfile.lock index 5be039431e..e77a7c9bea 100644 --- a/share/install_gems/Ubuntu2004/Gemfile.lock +++ b/share/install_gems/Ubuntu2004/Gemfile.lock @@ -65,6 +65,7 @@ GEM ffi-rzmq-core (>= 1.0.7) ffi-rzmq-core (1.0.7) ffi + gnuplot (2.6.2) hashie (3.6.0) highline (1.7.10) http-cookie (1.0.3) diff --git a/share/install_gems/install_gems b/share/install_gems/install_gems index b0c6faaa13..4977b936b3 100755 --- a/share/install_gems/install_gems +++ b/share/install_gems/install_gems @@ -59,7 +59,8 @@ GROUPS={ :market => 'aws-sdk', :onedb => ['mysql2', PG], :hooks => %w[zeromq ffi-rzmq], - :serversync => "augeas" + :serversync => "augeas", + :gnuplot => "gnuplot" } PACKAGES=GROUPS.keys @@ -83,7 +84,8 @@ DISTRIBUTIONS={ 'thin' => ['g++'], 'json' => ['gcc'], 'zeromq' => ['gcc', 'libzmq5', 'libzmq3-dev'], - 'augeas' => ['gcc', 'libaugeas-dev'] + 'augeas' => ['gcc', 'libaugeas-dev'], + 'gnuplot' => ['gcc'] }, :install_command_interactive => 'apt-get install', :install_command => 'apt-get -y install', @@ -105,7 +107,8 @@ DISTRIBUTIONS={ 'thin' => ['gcc-c++'], 'json' => ['gcc'], 'zeromq' => ['gcc', 'zeromq', 'zeromq-devel'], - 'augeas' => ['gcc', 'augeas-devel'] + 'augeas' => ['gcc', 'augeas-devel'], + 'gnuplot' => ['gcc'] }, :install_command_interactive => 'yum install', :install_command => 'yum -y install', diff --git a/src/cli/one_helper.rb b/src/cli/one_helper.rb index e389f34062..95131b6764 100644 --- a/src/cli/one_helper.rb +++ b/src/cli/one_helper.rb @@ -1203,6 +1203,18 @@ EOT end end + def OpenNebulaHelper.bytes_to_unit(value, unit = 'K') + j = 0 + i = BinarySufix.index(unit).to_i + + while j < i do + value /= 1024.0 + j += 1 + end + + value + end + # If the cluster name is empty, returns a '-' char. # # @param str [String || Hash] Cluster name, or empty Hash (when ) @@ -1887,4 +1899,57 @@ EOT answers end + # Returns plot object to print it on the CLI + # + # @param x [Array] Data to x axis (Time axis) + # @param y [Array] Data to y axis + # @param attr [String] Parameter to y axis + # @param title [String] Plot title + # + # @return Gnuplot plot object + def OpenNebulaHelper.get_plot(x, y, attr, title) + # Require gnuplot gem only here + begin + require 'gnuplot' + rescue LoadError, Gem::LoadError + STDERR.puts( + 'Gnuplot gem is not installed, run `gem install gnuplot` '\ + 'to install it' + ) + exit(-1) + end + + # Check if gnuplot is installed on the system + unless system('gnuplot --version') + STDERR.puts( + 'Gnuplot is not installed, install it depending on your distro' + ) + exit(-1) + end + + Gnuplot.open do |gp| + Gnuplot::Plot.new(gp) do |p| + p.title title + + p.xlabel 'Time' + p.ylabel attr + + p.xdata 'time' + p.timefmt "'%H:%M'" + p.format "x '%H:%M'" + + p.style 'data lines' + p.terminal 'dumb' + + p.data << Gnuplot::DataSet.new([x, y]) do |ds| + ds.with = 'linespoints' + ds.linewidth = '3' + ds.using = '1:2' + + ds.notitle + end + end + end + end + end diff --git a/src/cli/one_helper/onehost_helper.rb b/src/cli/one_helper/onehost_helper.rb index f767e71838..bbe2368395 100644 --- a/src/cli/one_helper/onehost_helper.rb +++ b/src/cli/one_helper/onehost_helper.rb @@ -17,6 +17,7 @@ require 'one_helper' require 'one_helper/onevm_helper' require 'rubygems' +require 'time' # implements onehost command class OneHostHelper < OpenNebulaHelper::OneHelper @@ -75,6 +76,15 @@ class OneHostHelper < OpenNebulaHelper::OneHelper VERSION_XPATH = "#{TEMPLATE_XPATH}/VERSION" + MONITORING = { + 'FREE_CPU' => 'CAPACITY', + 'FREE_MEMORY' => 'CAPACITY', + 'USED_CPU' => 'CAPACITY', + 'USED_MEMORY' => 'CAPACITY', + 'NETRX' => 'SYSTEM', + 'NETTX' => 'SYSTEM' + } + def self.rname 'HOST' end @@ -457,6 +467,92 @@ class OneHostHelper < OpenNebulaHelper::OneHelper end end + def monitoring(host, attr, options) + unit = options[:unit] || 'G' + start_d = options[:start] + end_d = options[:end] + n_elems = options[:n_elems] || 8 + + # Different available size units + units = %w[K M G T] + + # Attrs that need units conversion + attrs = %w[FREE_MEMORY USED_MEMORY] + + if unit && !units.include?(unit) + STDERR.puts "Invalid unit `#{unit}`" + exit(-1) + end + + attr = attr.upcase + start_d = Time.parse(start_d) if start_d + end_d = Time.parse(end_d) if end_d + + # Get monitoring data from user path + # + # 0 -> timestamp + # 1 -> data retrieved + monitoring_data = host.monitoring(["#{MONITORING[attr]}/#{attr}"]) + monitoring_data = monitoring_data["#{MONITORING[attr]}/#{attr}"] + + if monitoring_data.empty? + STDERR.puts 'No monitoring data found' + return + end + + # Get data max and min date + start_d ||= Time.at(monitoring_data.min {|v| v[0].to_i }[0].to_i) + end_d ||= Time.at(monitoring_data.max {|v| v[0].to_i }[0].to_i) + + # Filter data betwen dates + monitoring_data.reject! do |v| + v[0].to_i < start_d.to_i || v[0].to_i > end_d.to_i + end + + if monitoring_data.empty? + STDERR.puts "No monitoring data found between #{start_d} " \ + "and #{end_d}" + return + end + + start_d = start_d.strftime('%d/%m/%Y %H:%M') + end_d = end_d.strftime('%d/%m/%Y %H:%M') + + # Parse dcollected data + x = monitoring_data.collect {|v| Time.at(v[0].to_i).strftime('%H:%M') } + y = monitoring_data.collect do |v| + if attrs.include?(attr) + # GB is the default unit + v = OpenNebulaHelper.bytes_to_unit(v[1].to_i, unit).round(2) + "#{v} #{unit}B" + else + v[1] + end + end + + title = '' + title << "Host #{host.id} #{attr} " + title << "in #{unit}B " if unit && attrs.include?(attr) + title << "from #{start_d} to #{end_d}" + + x = x.last(n_elems) + y = y.last(n_elems) + + if options[:table] + print_monitoring_table(x, y, title) + elsif options[:csv] + csv = '' + + csv << "TIME#{options[:csv]}VALUE\n" + + x.zip(y) {|x_v, y_v| csv << "#{x_v}#{options[:csv]}#{y_v}\n" } + + puts csv + else + puts OpenNebulaHelper.get_plot(x, y, attr, title) + end + end + private def print_update_info(current, total, host) @@ -876,4 +972,30 @@ class OneHostHelper < OpenNebulaHelper::OneHelper table.show(hugepages) end + def print_monitoring_table(x, y, title) + puts + CLIHelper.print_header(title, true) + puts + + table = CLIHelper::ShowTable.new(nil, self) do + column :TIME, 'Timestamp', :size => 8, :left => false do |d| + d['TIME'] + end + + column :VALUE, 'Value', :size => 8, :left => false do |d| + d['VALUE'] + end + + default :TIME, :VALUE + end + + data = [] + + x.zip(y) do |x_v, y_v| + data << { 'TIME' => x_v, 'VALUE' => y_v } + end + + table.show(data) + end + end diff --git a/src/cli/onehost b/src/cli/onehost index f29bfb718e..333397118d 100755 --- a/src/cli/onehost +++ b/src/cli/onehost @@ -97,6 +97,52 @@ CommandParser::CmdParser.new(ARGV) do CREAT_OPTIONS = [IM, VMM, OneClusterHelper::CLUSTER, TYPE] SYNC_OPTIONS = [OneClusterHelper::CLUSTER, FORCE, SSH] + ######################################################################## + # Monitoring PLOT options + ######################################################################## + START_T = { + :name => 'start', + :large => '--start date', + :description => 'Start date to show data', + :format => String + } + + END_T = { + :name => 'end', + :large => '--end date', + :description => 'End date to show data', + :format => String + } + + UNIT = { + :name => 'unit', + :large => '--unit unit', + :description => 'Unit to format data', + :format => String + } + + TABLE = { + :name => 'table', + :large => '--table', + :description => 'Show monitoring information in table format' + } + + N_ELEMS = { + :name => 'n_elems', + :large => '--n elements', + :description => 'Number of records to show', + :format => Integer + } + + CSV_WITH_SEP = { + :name => 'csv', + :large => '--csv separator', + :description => 'Show data in CSV format', + :format => String + } + + PLOT_OPTS = [START_T, END_T, UNIT, TABLE, N_ELEMS, CSV_WITH_SEP] + ######################################################################## # Formatters for arguments ######################################################################## @@ -304,4 +350,25 @@ CommandParser::CmdParser.new(ARGV) do :options => [OneClusterHelper::CLUSTER] do helper.forceupdate(args[0], options) end + + monitoring_desc = <<-EOT.unindent + Show monitoring metrics in a graphic + EOT + + command :monitoring, + monitoring_desc, + :hostid, + :attr, + :options => PLOT_OPTS do + helper.perform_action(args[0], options, 'monitoring') do |host| + rc = host.info + + if OpenNebula.is_error?(rc) + STDERR.puts rc.message + exit(-1) + end + + helper.monitoring(host, args[1], options) + end + end end