lib/pghero/methods/query_stats.rb in pghero-2.6.0 vs lib/pghero/methods/query_stats.rb in pghero-2.7.0

- old
+ new

@@ -54,12 +54,50 @@ def disable_query_stats execute("DROP EXTENSION IF EXISTS pg_stat_statements") true end - def reset_query_stats(raise_errors: false) - execute("SELECT pg_stat_statements_reset()") + # TODO scope by database in PgHero 3.0 + # (add database: database_name to options) + def reset_query_stats(**options) + reset_instance_query_stats(**options) + end + + # resets query stats for the entire instance + # it's possible to reset stats for a specific + # database, user or query hash in Postgres 12+ + def reset_instance_query_stats(database: nil, user: nil, query_hash: nil, raise_errors: false) + if database || user || query_hash + raise PgHero::Error, "Requires PostgreSQL 12+" if server_version_num < 120000 + + if database + database_id = execute("SELECT oid FROM pg_database WHERE datname = #{quote(database)}").first.try(:[], "oid") + raise PgHero::Error, "Database not found: #{database}" unless database_id + else + database_id = 0 + end + + if user + user_id = execute("SELECT usesysid FROM pg_user WHERE usename = #{quote(user)}").first.try(:[], "usesysid") + raise PgHero::Error, "User not found: #{user}" unless user_id + else + user_id = 0 + end + + if query_hash + query_id = query_hash.to_i + # may not be needed + # but not intuitive that all query hashes are reset with 0 + raise PgHero::Error, "Invalid query hash: #{query_hash}" if query_id == 0 + else + query_id = 0 + end + + execute("SELECT pg_stat_statements_reset(#{quote(user_id.to_i)}, #{quote(database_id.to_i)}, #{quote(query_id.to_i)})") + else + execute("SELECT pg_stat_statements_reset()") + end true rescue ActiveRecord::StatementInvalid => e raise e if raise_errors false end @@ -102,35 +140,36 @@ query_stats = {} mapping.each do |database_id, database_name| query_stats[database_id] = query_stats(limit: 1000000, database: database_name) end - supports_query_hash = supports_query_hash? + query_stats = query_stats.select { |_, v| v.any? } - if query_stats.any? { |_, v| v.any? } && reset_query_stats(raise_errors: raise_errors) - query_stats.each do |db_id, db_query_stats| - if db_query_stats.any? - values = - db_query_stats.map do |qs| - [ - db_id, - qs[:query], - qs[:total_minutes] * 60 * 1000, - qs[:calls], - now, - supports_query_hash ? qs[:query_hash] : nil, - qs[:user] - ] - end + # nothing to do + return if query_stats.empty? - columns = %w[database query total_time calls captured_at query_hash user] - insert_stats("pghero_query_stats", columns, values) + # use mapping, not query stats here + # TODO add option for this, and make default in PgHero 3.0 + if false # mapping.size == 1 && server_version_num >= 120000 + query_stats.each do |db_id, db_query_stats| + if reset_query_stats(database: mapping[db_id], raise_errors: raise_errors) + insert_query_stats(db_id, db_query_stats, now) end end + else + if reset_query_stats(raise_errors: raise_errors) + query_stats.each do |db_id, db_query_stats| + insert_query_stats(db_id, db_query_stats, now) + end + end end end + def clean_query_stats + PgHero::QueryStats.where(database: id).where("captured_at < ?", 14.days.ago).delete_all + end + def slow_queries(query_stats: nil, **options) query_stats ||= self.query_stats(options) query_stats.select { |q| q[:calls].to_i >= slow_query_calls.to_i && q[:average_time].to_f >= slow_query_ms.to_f } end @@ -284,9 +323,27 @@ # removes comments # combines ?, ?, ? => ? def normalize_query(query) squish(query.to_s.gsub(/\?(, ?\?)+/, "?").gsub(/\/\*.+?\*\//, "")) + end + + def insert_query_stats(db_id, db_query_stats, now) + values = + db_query_stats.map do |qs| + [ + db_id, + qs[:query], + qs[:total_minutes] * 60 * 1000, + qs[:calls], + now, + supports_query_hash? ? qs[:query_hash] : nil, + qs[:user] + ] + end + + columns = %w[database query total_time calls captured_at query_hash user] + insert_stats("pghero_query_stats", columns, values) end end end end