diff --git a/Readme.md b/Readme.md index b1636b8..9b5eafb 100644 --- a/Readme.md +++ b/Readme.md @@ -10,7 +10,25 @@ A heroku plugin for awesome pg:* commands that are also great and fun and super. ### Usage + ``` bash +~ ➤ heroku pg:diagnose -a will +Report abc123 for will::HEROKU_POSTGRESQL_MAROON_URL +available for one month after creation on 2014-05-30 20:52:09.00093+00 + +RED: Hit Rate +Name Ratio +-------------- -------------------- +index hit rate 0.920046133853151396 + +GREEN: Connection Count +GREEN: Unused Indexes +GREEN: Bloat +GREEN: Load +GREEN: Long Queries +GREEN: Idle in Transaction +GREEN: Blocking Queries + ~ ➤ heroku pg:cache-hit -adashboard name | ratio ----------------+------------------------ diff --git a/init.rb b/init.rb index b764be3..051f45d 100644 --- a/init.rb +++ b/init.rb @@ -1,11 +1,13 @@ require "heroku/command/base" -if Heroku::VERSION.split(/\./).first.to_i < 3 +major, minor, _ = Heroku::VERSION.split(/\./).map(&:to_i) +if (major > 3) || (major == 3 && minor >= 4) + require File.expand_path('lib/heroku/client/heroku_postgresql', File.dirname(__FILE__)) + require File.expand_path('lib/heroku/command/pg', File.dirname(__FILE__)) + require File.expand_path('lib/heroku/command/pgdiagnose', File.dirname(__FILE__)) +else $stderr.puts(Heroku::Helpers.format_with_bang(<<-EOM)) The heroku-pg-extras plugin was not loaded. -It requires Heroku CLI version >= 3.0.0. You are using #{Heroku::VERSION}. +It requires Heroku CLI version >= 3.4.0. You are using #{Heroku::VERSION}. EOM -else - require File.expand_path('lib/heroku/client/heroku_postgresql', File.dirname(__FILE__)) - require File.expand_path('lib/heroku/command/pg', File.dirname(__FILE__)) end diff --git a/lib/heroku/client/heroku_postgresql.rb b/lib/heroku/client/heroku_postgresql.rb index ca202dd..d2588f2 100644 --- a/lib/heroku/client/heroku_postgresql.rb +++ b/lib/heroku/client/heroku_postgresql.rb @@ -1,4 +1,8 @@ class Heroku::Client::HerokuPostgresql + def metrics + http_get "#{resource_name}/metrics" + end + def upgrade http_post "#{resource_name}/upgrade" end diff --git a/lib/heroku/command/pgdiagnose.rb b/lib/heroku/command/pgdiagnose.rb new file mode 100644 index 0000000..b556663 --- /dev/null +++ b/lib/heroku/command/pgdiagnose.rb @@ -0,0 +1,104 @@ +class Heroku::Command::Pg < Heroku::Command::Base + DIAGNOSE_URL = ENV.fetch('PGDIAGNOSE_URL', "https://pgdiagnose.herokuapp.com") + # pg:diagnose [DATABASE|REPORT_ID] + # + # run diagnostics report on DATABASE + # + # defaults to DATABASE_URL databases if no DATABASE is specified + # if REPORT_ID is specified instead, a previous report id displayed + def diagnose + db_id = shift_argument + + track_extra('diagnose') if can_track? + report = find_or_generate_report(db_id) + + puts "Report #{report["id"]} for #{report["app"]}::#{report["database"]}" + puts "available for one month after creation on #{report["created_at"]}" + puts + + c = report['checks'] + process_checks 'red', c.select{|f| f['status'] == 'red'} + process_checks 'yellow', c.select{|f| f['status'] == 'yellow'} + process_checks 'green', c.select{|f| f['status'] == 'green'} + process_checks 'unknown', c.reject{|f| %w(red yellow green).include?(f['status'])} + end + + private + + def find_or_generate_report(db_id) + if db_id =~ /\A[a-z0-9\-]{36}\z/ + response = get_report(db_id) + else + response = generate_report(db_id) + end + + JSON.parse(response.body) + rescue Excon::Errors::Error + error("Unable to connect to PGDiagnose API, please try again later") + end + + def get_report(report_id) + Excon.get("#{DIAGNOSE_URL}/reports/#{report_id}", :headers => {"Content-Type" => "application/json"}) + end + + def generate_report(db_id) + attachment = generate_resolver.resolve(db_id, "DATABASE_URL") + validate_arguments! + + + @uri = URI.parse(attachment.url) # for nine_two? + if !nine_two? + warn "WARNING: pg:diagnose is only fully suppoted on Postgres version >= 9.2. Some checks will be skipped.\n\n" + end + + if attachment.starter_plan? + metrics = nil + else + metrics = hpg_client(attachment).metrics + end + + params = { + 'url' => attachment.url, + 'plan' => attachment.plan, + 'metrics' => metrics, + 'app' => attachment.app, + 'database' => attachment.config_var + } + + return Excon.post("#{DIAGNOSE_URL}/reports", :body => params.to_json, :headers => {"Content-Type" => "application/json"}) + end + + def color(message, status) + if $stdout.tty? + color_code = { "red" => 31, "green" => 32, "yellow" => 33 }.fetch(status, 35) + return "\e[#{color_code}m#{message}\e[0m" + else + return message + end + end + + def process_checks(status, checks) + return unless checks.size > 0 + + checks.each do |check| + status = check['status'] + puts color("#{status.upcase}: #{check['name']}", status) + next if "green" == status + + results = check['results'] + return unless results && results.size > 0 + + if results.first.kind_of? Array + puts " " + results.first.map(&:capitalize).join(" ") + else + display_table( + results, + results.first.keys, + results.first.keys.map{ |field| field.split(/_/).map(&:capitalize).join(' ') } + ) + end + puts + end + end +end +