# -*- encoding: utf-8 -*- require 'thor' class GrowthForecast::CLI < Thor class_option :silent, :aliases => ["-S"], :type => :boolean def initialize(args = [], opts = [], config = {}) super(args, opts, config) end desc 'post <json> <api_url>', 'post a paramter to graph api' long_desc <<-LONGDESC Post a parameter to graph api ex) growthforecast-client post '{"number":0}' http://{hostname}:{port}/api/{service_name}/{section_name}/{graph_name} LONGDESC def post(json, url) base_uri, service_name, section_name, graph_name = split_url(url) @client = client(base_uri) puts @client.post_graph(service_name, section_name, graph_name, JSON.parse(json)) end desc 'delete <url>', 'delete a graph or graphs under a url' long_desc <<-LONGDESC Delete a graph or graphs under a <url> where <url> is the one obtained from the GrowthForecast URI, e.g., http://{hostname}:{port}/list/{service_name}/{section_name}?t=sh or http://{hostname}:{port}/view_graph/{service_name}/{section_name}/{graph_name}?t=sh ex) growthforecast-client delete 'http://{hostname}:{port}/list/{service_name}/{section_name}' LONGDESC option :graph_names, :type => :array, :aliases => '-g' option :section_names, :type => :array, :aliases => '-s' def delete(url) section_names, graph_names = options[:section_names], options[:graph_names] base_uri, service_name, section_name, graph_name = split_url(url) @client = client(base_uri) graphs = @client.list_graph(service_name, section_name, graph_name) delete_graphs(graphs, graph_names, section_names) complexes = @client.list_complex(service_name, section_name, graph_name) delete_complexes(complexes, graph_names, section_names) end desc 'color <url>', 'change the color of graphs under url' long_desc <<-LONGDESC Change the color of graphs ex) growthforecast-client color 'http://{hostname}:{port}/list/{service_name}/{section_name}' -c '2xx_count:#1111cc' '3xx_count:#11cc11' LONGDESC option :colors, :type => :hash, :aliases => '-c', :required => true, :banner => 'GRAPH_NAME:COLOR ...' def color(url) colors = options[:colors] base_uri, service_name, section_name, graph_name = split_url(url) @client = client(base_uri) graphs = @client.list_graph(service_name, section_name, graph_name) setup_colors(colors, graphs) end desc 'create_complex <url>', 'create complex graphs under url' long_desc <<-LONGDESC Create complex graphs under a url ex) growthforecast-client create_complex 'http://{hostname}:{port}/list/{service_name}' -f 2xx_count 3xx_count -t status_count LONGDESC option :from_graphs, :type => :array, :aliases => '-f', :required => true, :banner => 'GRAPH_NAMES ...' option :to_complex, :type => :string, :aliases => '-t', :required => true def create_complex(url) from_graphs, to_complex = options[:from_graphs], options[:to_complex] base_uri, service_name, section_name, graph_name = split_url(url) @client = client(base_uri) graphs = @client.list_graph(service_name, section_name, graph_name) setup_complex(from_graphs, to_complex, graphs) end desc 'vrule <json> <api_url>', 'create a vertical line' long_desc <<-LONGDESC Create a vertical line ex) growthforecast-client vrule '{"dashes":"2,10"}' http://{hostname}:{port}/vrule/api[/{service_name}[/{section_name}[/{graph_name}]]] LONGDESC def vrule(json, url) base_uri, service_name, section_name, graph_name = split_url(url) @client = client(base_uri) puts @client.post_vrule(service_name, section_name, graph_name, JSON.parse(json)) end desc 'bench <api_url>', 'benchmark the GrowthForecast' long_desc <<-LONGDESC Benchmark the GrowthForecast. ex) growthforecast-client bench http://{hostname}:{port} -n 100 -c 10 LONGDESC option :requests, :type => :numeric, :aliases => '-n', :default => 100 option :concurrency, :type => :numeric, :aliases => '-c', :default => 1 def bench(url) requests, concurrency = options[:requests], options[:concurrency] require 'parallel' base_uri = split_url(url).first @client = client(base_uri) # generate unique paths of the same number with number of requests beforehand paths = [] requests.times do |i| paths << [(i/100).to_s, (i/50).to_s, i.to_s] end # Take a benchmark in parallel start = Time.now Parallel.each_with_index(paths, :in_processes => concurrency) do |path, i| puts "Completed #{i} requests" if i % 1000 == 0 and i > 0 begin @client.post_graph(path[0], path[1], path[2], { "number" => rand(1000) }) rescue => e $stderr.puts "#{e.class} #{e.message}" end end puts "Completed #{requests} requests" duration = (Time.now - start).to_f puts "Requests per second: #{requests / duration} [#/sec] (mean)" puts "Time per request: #{duration / requests * 1000} [ms] (mean)" end no_tasks do def delete_graphs(graphs, graph_names = nil, section_names = nil) graphs.each do |graph| service_name, section_name, graph_name = graph['service_name'], graph['section_name'], graph['graph_name'] next if section_names and !section_names.include?(section_name) next if graph_names and !graph_names.include?(graph_name) puts "Delete #{service_name}/#{section_name}/#{graph_name}" unless @options[:silent] exec { @client.delete_graph(service_name, section_name, graph_name) } end end def delete_complexes(complexes, graph_names = nil, section_names = nil) complexes.each do |graph| service_name, section_name, graph_name = graph['service_name'], graph['section_name'], graph['graph_name'] next if section_names and !section_names.include?(section_name) next if graph_names and !graph_names.include?(graph_name) puts "Delete #{service_name}/#{section_name}/#{graph_name}" unless @options[:silent] exec { @client.delete_complex(service_name, section_name, graph_name) } end end def setup_colors(colors, graphs) graphs.each do |graph| service_name, section_name, graph_name = graph['service_name'], graph['section_name'], graph['graph_name'] next unless color = colors[graph_name] params = { 'color' => color } puts "Setup #{service_name}/#{section_name}/#{graph_name} with #{color}" unless @options[:silent] exec { @client.edit_graph(service_name, section_name, graph_name, params) } end end def setup_complex(from_graphs, to_complex, graphs) from_graph_first = from_graphs.first graphs.each do |graph| service_name, section_name, graph_name = graph['service_name'], graph['section_name'], graph['graph_name'] next unless graph_name == from_graph_first base = { "service_name" => service_name, "section_name" => section_name, "gmode" => 'gauge', "stack" => true, "type" => 'AREA' } from_graphs_params = from_graphs.map {|graph_name| base.merge('graph_name' => graph_name) } to_complex_params = { "service_name" => service_name, "section_name" => section_name, "graph_name" => to_complex, "sort" => 0 } puts "Setup #{service_name}/#{section_name}/#{to_complex} with #{from_graphs}" unless @options[:silent] exec { @client.create_complex(from_graphs_params, to_complex_params) } end end def exec(&blk) begin yield rescue => e $stderr.puts "\tclass:#{e.class}\t#{e.message}" end end def client(base_uri) GrowthForecast::Client.new(base_uri) end def split_url(url) uri = URI.parse(url) base_uri = "#{uri.scheme}://#{uri.host}:#{uri.port}" [base_uri] + split_path(uri.path) end def split_path(path) path = path.gsub(/.*list\/?/, '').gsub(/.*view_graph\/?/, '').gsub(/.*api\/?/, '') path.split('/').map {|p| CGI.unescape(p.gsub('%20', '+')) } end end end