module PgHero

Constants

MUTEX
VERSION

Attributes

cache_hit_rate_threshold[RW]
config_path[RW]
env[RW]
explain_timeout_sec[RW]
filter_data[RW]
long_running_query_sec[RW]
show_migrations[RW]
slow_query_calls[RW]
slow_query_ms[RW]
total_connections_threshold[RW]

Public Class Methods

analyze_all(**options) click to toggle source
# File lib/pghero.rb, line 176
def analyze_all(**options)
  each_database do |database|
    next if database.replica?
    database.analyze_tables(**options)
  end
end
autoindex_all(create: false, verbose: true) click to toggle source
# File lib/pghero.rb, line 183
def autoindex_all(create: false, verbose: true)
  each_database do |database|
    puts "Autoindexing #{database.id}..." if verbose
    database.autoindex(create: create)
  end
end
capture_query_stats(verbose: false) click to toggle source
# File lib/pghero.rb, line 161
def capture_query_stats(verbose: false)
  each_database do |database|
    next unless database.capture_query_stats?
    puts "Capturing query stats for #{database.id}..." if verbose
    database.capture_query_stats(raise_errors: true)
  end
end
capture_space_stats(verbose: false) click to toggle source
# File lib/pghero.rb, line 169
def capture_space_stats(verbose: false)
  each_database do |database|
    puts "Capturing space stats for #{database.id}..." if verbose
    database.capture_space_stats
  end
end
clean_query_stats() click to toggle source

delete previous stats go database by database to use an index stats for old databases are not cleaned up since we can't use an index

# File lib/pghero.rb, line 197
def clean_query_stats
  each_database do |database|
    database.clean_query_stats
  end
end
clean_space_stats() click to toggle source
# File lib/pghero.rb, line 203
def clean_space_stats
  each_database do |database|
    database.clean_space_stats
  end
end
config() click to toggle source
# File lib/pghero.rb, line 93
def config
  @config ||= begin
    require "erb"
    require "yaml"

    path = config_path

    config_file_exists = File.exist?(path)

    config = YAML.load(ERB.new(File.read(path)).result) if config_file_exists
    config ||= {}

    if config[env]
      config[env]
    elsif config["databases"] # preferred format
      config
    elsif config_file_exists
      raise "Invalid config file"
    else
      databases = {}

      if !ENV["PGHERO_DATABASE_URL"] && spec_supported?
        ActiveRecord::Base.configurations.configs_for(env_name: env, include_replicas: true).each do |db|
          databases[db.send(spec_name_key)] = {"spec" => db.send(spec_name_key)}
        end
      end

      if databases.empty?
        databases["primary"] = {
          "url" => ENV["PGHERO_DATABASE_URL"] || connection_config(ActiveRecord::Base)
        }
      end

      if databases.size == 1
        databases.values.first.merge!(
          "db_instance_identifier" => ENV["PGHERO_DB_INSTANCE_IDENTIFIER"],
          "gcp_database_id" => ENV["PGHERO_GCP_DATABASE_ID"],
          "azure_resource_id" => ENV["PGHERO_AZURE_RESOURCE_ID"]
        )
      end

      {
        "databases" => databases
      }
    end
  end
end
connection_config(model) click to toggle source

private

# File lib/pghero.rb, line 215
def connection_config(model)
  ActiveRecord::VERSION::STRING.to_f >= 6.1 ? model.connection_db_config.configuration_hash : model.connection_config
end
databases() click to toggle source

ensure we only have one copy of databases so there's only one connection pool per database

# File lib/pghero.rb, line 143
def databases
  unless defined?(@databases)
    # only use mutex on initialization
    MUTEX.synchronize do
      # return if another process initialized while we were waiting
      return @databases if defined?(@databases)

      @databases = config["databases"].map { |id, c| [id.to_sym, Database.new(id, c)] }.to_h
    end
  end

  @databases
end
password() click to toggle source

use method instead of attr_accessor to ensure this works if variable set after PgHero is loaded

# File lib/pghero.rb, line 85
def password
  @password ||= config["password"] || ENV["PGHERO_PASSWORD"]
end
pretty_size(value) click to toggle source
# File lib/pghero.rb, line 190
def pretty_size(value)
  ActiveSupport::NumberHelper.number_to_human_size(value, precision: 3)
end
primary_database() click to toggle source
# File lib/pghero.rb, line 157
def primary_database
  databases.values.first
end
spec_name_key() click to toggle source

private Rails 6.1 deprecate `spec_name` and use `name` for configurations github.com/rails/rails/pull/38536

# File lib/pghero.rb, line 222
def spec_name_key
  ActiveRecord::VERSION::STRING.to_f >= 6.1 ? :name : :spec_name
end
spec_supported?() click to toggle source

private

# File lib/pghero.rb, line 210
def spec_supported?
  ActiveRecord::VERSION::MAJOR >= 6
end
stats_database_url() click to toggle source
# File lib/pghero.rb, line 89
def stats_database_url
  @stats_database_url ||= config["stats_database_url"] || ENV["PGHERO_STATS_DATABASE_URL"]
end
time_zone() click to toggle source
# File lib/pghero.rb, line 73
def time_zone
  @time_zone || Time.zone
end
time_zone=(time_zone) click to toggle source
# File lib/pghero.rb, line 69
def time_zone=(time_zone)
  @time_zone = time_zone.is_a?(ActiveSupport::TimeZone) ? time_zone : ActiveSupport::TimeZone[time_zone.to_s]
end
username() click to toggle source

use method instead of attr_accessor to ensure this works if variable set after PgHero is loaded

# File lib/pghero.rb, line 79
def username
  @username ||= config["username"] || ENV["PGHERO_USERNAME"]
end

Private Class Methods

each_database() { |database| ... } click to toggle source
# File lib/pghero.rb, line 228
def each_database
  first_error = nil

  databases.each do |_, database|
    begin
      yield database
    rescue => e
      puts "#{e.class.name}: #{e.message}"
      puts
      first_error ||= e
    end
  end

  raise first_error if first_error

  true
end