ruby-pg-extras 2.1.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a88ff7e79fc4b77781012f9ee52afeee2462dcc46177d6bec8d24885c9822526
4
- data.tar.gz: 9befdd7dd6f98195391fe323f5fabf0d74a6bb9bb0d717f7cf74c30219f91462
3
+ metadata.gz: 1b09fa8f29c8e1b0438acf8834d8d51e4ab76b2b8c4c7475e7ac82902d9bc29d
4
+ data.tar.gz: 5a18583d3576f515ea05e0a5d74180c11d811a44d5ecca9d5bad001540f28a98
5
5
  SHA512:
6
- metadata.gz: 0c5df44798b60ae20ad1d8bb395cce56d92e89ddde8912d0a747fcf7e695c74ea3f9a58fa71b7d570a956d908ef3261bfc776c8feb23b21a7f61c595bc3a0855
7
- data.tar.gz: d8d2ddcc09c7127ff75b8897bbef5a4ad98f057c80e836bcf8849833c0580914bfd1fd3a435cce34ef4124458dcf0fb12b3dcdb1d78b6932bd629f0ddb90a2f5
6
+ metadata.gz: 3a24a0a49e083f081d0d023f4f1b05d3fa867ef5b362f99eb6bc3c711616b857c5f434e5c23ffcc2ba42a0890f562f553694cb45434fa5484402569a827fe1fa
7
+ data.tar.gz: c50dd6d08c4e470a53257af9a7092271a6abeaab308579dc579efcd7df39e9b42031cc01f3d627225fcc73a26bb5c3253a6fe2c94ccc19812fc9671d88936a9b
data/.circleci/config.yml CHANGED
@@ -31,7 +31,8 @@ jobs:
31
31
  - checkout
32
32
  - run: gem update --system
33
33
  - run: gem install bundler
34
- - run: bundle install --path vendor/bundle
34
+ - run: bundle config set --local path 'vendor/bundle'
35
+ - run: bundle install
35
36
  - run: sudo apt-get update --allow-releaseinfo-change
36
37
  - run: sudo apt install postgresql-client-11
37
38
  - run: dockerize -wait tcp://postgres11:5432 -timeout 1m
data/README.md CHANGED
@@ -26,7 +26,7 @@ In your Gemfile
26
26
  gem "ruby-pg-extras"
27
27
  ```
28
28
 
29
- Some of the queries (e.g., `calls` and `outliers`) require [pg_stat_statements](https://www.postgresql.org/docs/current/pgstatstatements.html) extension enabled.
29
+ `calls` and `outliers` queries require [pg_stat_statements](https://www.postgresql.org/docs/current/pgstatstatements.html) extension.
30
30
 
31
31
  You can check if it is enabled in your database by running:
32
32
 
@@ -39,6 +39,12 @@ You should see the similar line in the output:
39
39
  | pg_stat_statements | 1.7 | 1.7 | track execution statistics of all SQL statements executed |
40
40
  ```
41
41
 
42
+ `ssl_used` requires `sslinfo` extension, and `buffercache_usage`/`buffercache_usage` queries need `pg_buffercache`. You can enable them all by running:
43
+
44
+ ```ruby
45
+ RubyPGExtras.add_extensions
46
+ ```
47
+
42
48
  ## Usage
43
49
 
44
50
  Gem expects the `ENV['DATABASE_URL']` value in the following format:
@@ -92,6 +98,18 @@ RubyPGExtras.long_running_queries(args: { threshold: "200 milliseconds" })
92
98
 
93
99
  ```
94
100
 
101
+ ## Diagnose report
102
+
103
+ The simplest way to start using pg-extras is to execute a `diagnose` method. It runs a set of checks and prints out a report highlighting areas that may require additional investigation:
104
+
105
+ ```ruby
106
+ RubyPGExtras.diagnose
107
+ ```
108
+
109
+ ![Diagnose report](https://github.com/pawurb/ruby-pg-extras/raw/master/ruby-pg-extras-diagnose.png)
110
+
111
+ Keep reading to learn about methods that `diagnose` uses under the hood.
112
+
95
113
  ## Available methods
96
114
 
97
115
  ### `cache_hit`
@@ -167,6 +185,20 @@ This method displays values for selected PostgreSQL settings. You can compare th
167
185
 
168
186
  [More info](https://pawelurbanek.com/postgresql-fix-performance#cache-hit)
169
187
 
188
+ ### `ssl_used`
189
+
190
+ ```ruby
191
+
192
+ RubyPGExtras.ssl_used
193
+
194
+ | ssl_is_used |
195
+ +---------------------------------+
196
+ | t |
197
+
198
+ ```
199
+
200
+ Returns boolean indicating if an encrypted SSL is currently used. Connecting to the database via an unencrypted connection is a critical security risk.
201
+
170
202
  ### `index_usage`
171
203
 
172
204
  ```ruby
@@ -222,7 +254,7 @@ This command displays all the current locks, regardless of their type.
222
254
 
223
255
  RubyPGExtras.outliers(args: { limit: 20 })
224
256
 
225
- qry | exec_time | prop_exec_time | ncalls | sync_io_time
257
+ query | exec_time | prop_exec_time | ncalls | sync_io_time
226
258
  -----------------------------------------+------------------+----------------+-------------+--------------
227
259
  SELECT * FROM archivable_usage_events.. | 154:39:26.431466 | 72.2% | 34,211,877 | 00:00:00
228
260
  COPY public.archivable_usage_events (.. | 50:38:33.198418 | 23.6% | 13 | 13:34:21.00108
@@ -373,7 +405,7 @@ This command displays the total size of each table and materialized view in the
373
405
 
374
406
  ```ruby
375
407
 
376
- RubyPGExtras.unused_indexes(args: { min_scans: 20 })
408
+ RubyPGExtras.unused_indexes(args: { max_scans: 20 })
377
409
 
378
410
  table | index | index_size | index_scans
379
411
  ---------------------+--------------------------------------------+------------+-------------
@@ -527,6 +559,14 @@ RubyPGExtras.kill_all
527
559
 
528
560
  This commands kills all the currently active connections to the database. It can be useful as a last resort when your database is stuck in a deadlock.
529
561
 
562
+ ### `pg_stat_statements_reset`
563
+
564
+ ```ruby
565
+ RubyPGExtras.pg_stat_statements_reset
566
+ ```
567
+
568
+ This command discards all statistics gathered so far by pg_stat_statements.
569
+
530
570
  ### `buffercache_stats`
531
571
 
532
572
  ```ruby
@@ -0,0 +1,228 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'filesize'
4
+
5
+ module RubyPGExtras
6
+ class DiagnoseData
7
+ PG_EXTRAS_TABLE_CACHE_HIT_MIN_EXPECTED = "0.985"
8
+ PG_EXTRAS_INDEX_CACHE_HIT_MIN_EXPECTED = "0.985"
9
+ PG_EXTRAS_UNUSED_INDEXES_MAX_SCANS = 20
10
+ PG_EXTRAS_UNUSED_INDEXES_MIN_SIZE_BYTES = Filesize.from("1 MB").to_i # 1000000 bytes
11
+ PG_EXTRAS_NULL_INDEXES_MIN_SIZE_MB = 1 # 1 MB
12
+ PG_EXTRAS_NULL_MIN_NULL_FRAC_PERCENT = 50 # 50%
13
+ PG_EXTRAS_BLOAT_MIN_VALUE = 10
14
+ PG_EXTRAS_OUTLIERS_MIN_EXEC_RATIO = 33 # 33%
15
+
16
+ def self.call
17
+ new.call
18
+ end
19
+
20
+ def call
21
+ [
22
+ :table_cache_hit,
23
+ :index_cache_hit,
24
+ :unused_indexes,
25
+ :null_indexes,
26
+ :bloat,
27
+ :duplicate_indexes
28
+ ].yield_self do |checks|
29
+ extensions_data = query_module.extensions(in_format: :hash)
30
+ pg_stats_enabled = extensions_data.find do |el|
31
+ el.fetch("name") == "pg_stat_statements"
32
+ end.fetch("installed_version", false)
33
+
34
+ ssl_info_enabled = extensions_data.find do |el|
35
+ el.fetch("name") == "sslinfo"
36
+ end.fetch("installed_version", false)
37
+
38
+ if pg_stats_enabled
39
+ checks = checks.concat([:outliers])
40
+ end
41
+
42
+ if ssl_info_enabled
43
+ checks = checks.concat([:ssl_used])
44
+ end
45
+
46
+ checks
47
+ end.map do |check|
48
+ send(check).merge(check_name: check)
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def query_module
55
+ RubyPGExtras
56
+ end
57
+
58
+ def table_cache_hit
59
+ min_expected = ENV.fetch(
60
+ "PG_EXTRAS_TABLE_CACHE_HIT_MIN_EXPECTED",
61
+ PG_EXTRAS_TABLE_CACHE_HIT_MIN_EXPECTED
62
+ ).to_f
63
+
64
+ table_cache_hit_ratio = query_module.cache_hit(in_format: :hash)[1].fetch("ratio").to_f.round(6)
65
+
66
+ if table_cache_hit_ratio > min_expected
67
+ {
68
+ ok: true,
69
+ message: "Table cache hit ratio is correct: #{table_cache_hit_ratio}."
70
+ }
71
+ else
72
+ {
73
+ ok: false,
74
+ message: "Table hit ratio is too low: #{table_cache_hit_ratio}."
75
+ }
76
+ end
77
+ end
78
+
79
+ def index_cache_hit
80
+ min_expected = ENV.fetch(
81
+ "PG_EXTRAS_INDEX_CACHE_HIT_MIN_EXPECTED",
82
+ PG_EXTRAS_INDEX_CACHE_HIT_MIN_EXPECTED
83
+ ).to_f
84
+
85
+ index_cache_hit_ratio = query_module.cache_hit(in_format: :hash)[0].fetch("ratio").to_f.round(6)
86
+
87
+ if index_cache_hit_ratio > min_expected
88
+ {
89
+ ok: true,
90
+ message: "Index hit ratio is correct: #{index_cache_hit_ratio}."
91
+ }
92
+ else
93
+ {
94
+ ok: false,
95
+ message: "Index hit ratio is too low: #{index_cache_hit_ratio}."
96
+ }
97
+ end
98
+ end
99
+
100
+ def ssl_used
101
+ ssl_connection = query_module.ssl_used(in_format: :hash)[0].fetch("ssl_is_used")
102
+
103
+ if ssl_connection
104
+ {
105
+ ok: true,
106
+ message: "Database client is using a secure SSL connection."
107
+ }
108
+ else
109
+ {
110
+ ok: false,
111
+ message: "Database client is using an unencrypted connection."
112
+ }
113
+ end
114
+ end
115
+
116
+ def unused_indexes
117
+ indexes = query_module.unused_indexes(
118
+ in_format: :hash,
119
+ args: { min_scans: PG_EXTRAS_UNUSED_INDEXES_MAX_SCANS }
120
+ ).select do |i|
121
+ Filesize.from(i.fetch("index_size")).to_i >= PG_EXTRAS_UNUSED_INDEXES_MIN_SIZE_BYTES
122
+ end
123
+
124
+ if indexes.count == 0
125
+ {
126
+ ok: true,
127
+ message: "No unused indexes detected."
128
+ }
129
+ else
130
+ print_indexes = indexes.map do |i|
131
+ "'#{i.fetch('index')}' on '#{i.fetch('table')}' size #{i.fetch('index_size')}"
132
+ end.join(",\n")
133
+ {
134
+ ok: false,
135
+ message: "Unused indexes detected:\n#{print_indexes}"
136
+ }
137
+ end
138
+ end
139
+
140
+ def null_indexes
141
+ indexes = query_module.null_indexes(
142
+ in_format: :hash,
143
+ args: { min_relation_size_mb: PG_EXTRAS_NULL_INDEXES_MIN_SIZE_MB }
144
+ ).select do |i|
145
+ i.fetch("null_frac").gsub("%", "").to_f >= PG_EXTRAS_NULL_MIN_NULL_FRAC_PERCENT
146
+ end
147
+
148
+ if indexes.count == 0
149
+ {
150
+ ok: true,
151
+ message: "No null indexes detected."
152
+ }
153
+ else
154
+ print_indexes = indexes.map do |i|
155
+ "'#{i.fetch('index')}' size #{i.fetch('index_size')} null values fraction #{i.fetch('null_frac')}"
156
+ end.join(",\n")
157
+ {
158
+ ok: false,
159
+ message: "Null indexes detected:\n#{print_indexes}"
160
+ }
161
+ end
162
+ end
163
+
164
+ def bloat
165
+ bloat_data = query_module.bloat(in_format: :hash).select do |b|
166
+ b.fetch("bloat").to_f >= PG_EXTRAS_BLOAT_MIN_VALUE
167
+ end
168
+
169
+ if bloat_data.count == 0
170
+ {
171
+ ok: true,
172
+ message: "No bloat detected."
173
+ }
174
+ else
175
+ print_bloat = bloat_data.map do |b|
176
+ "'#{b.fetch('object_name')}' bloat #{b.fetch('bloat')} waste #{b.fetch('waste')}"
177
+ end.join(",\n")
178
+
179
+ {
180
+ ok: false,
181
+ message: "Bloat detected:\n#{print_bloat}"
182
+ }
183
+ end
184
+ end
185
+
186
+ def duplicate_indexes
187
+ indexes = query_module.duplicate_indexes(in_format: :hash)
188
+
189
+ if indexes.count == 0
190
+ {
191
+ ok: true,
192
+ message: "No duplicate indexes detected."
193
+ }
194
+ else
195
+ print_indexes = indexes.map do |i|
196
+ "'#{i.fetch('idx1')}' of size #{i.fetch('size')} is identical to '#{i.fetch('idx2')}'"
197
+ end.join(",\n")
198
+
199
+ {
200
+ ok: false,
201
+ message: "Duplicate indexes detected:\n#{print_indexes}"
202
+ }
203
+ end
204
+ end
205
+
206
+ def outliers
207
+ queries = query_module.outliers(in_format: :hash).select do |q|
208
+ q.fetch("prop_exec_time").gsub("%", "").to_f >= PG_EXTRAS_OUTLIERS_MIN_EXEC_RATIO
209
+ end
210
+
211
+ if queries.count == 0
212
+ {
213
+ ok: true,
214
+ message: "No queries using significant execution ratio detected."
215
+ }
216
+ else
217
+ print_queries = queries.map do |q|
218
+ "'#{q.fetch('query').slice(0, 30)}...' called #{q.fetch('ncalls')} times, using #{q.fetch('prop_exec_time')} of total exec time."
219
+ end.join(",\n")
220
+
221
+ {
222
+ ok: false,
223
+ message: "Queries using significant execution ratio detected:\n#{print_queries}"
224
+ }
225
+ end
226
+ end
227
+ end
228
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'terminal-table'
4
+
5
+ module RubyPGExtras
6
+ class DiagnosePrint
7
+ def self.call(data)
8
+ new.call(data)
9
+ end
10
+
11
+ def call(data)
12
+ rows = data.sort do |el|
13
+ p el
14
+ el.fetch(:ok) ? 1 : -1
15
+ end.map do |el|
16
+ symbol = el.fetch(:ok) ? "√" : "x"
17
+ color = el.fetch(:ok) ? :green : :red
18
+
19
+ [
20
+ colorize("[#{symbol}] - #{el.fetch(:check_name)}", color),
21
+ colorize(el.fetch(:message), color)
22
+ ]
23
+ end
24
+
25
+ puts Terminal::Table.new(
26
+ title: title,
27
+ rows: rows
28
+ )
29
+ end
30
+
31
+ private
32
+
33
+ def title
34
+ "ruby-pg-extras - diagnose report"
35
+ end
36
+
37
+ def colorize(string, color)
38
+ if color == :red
39
+ "\e[0;31;49m#{string}\e[0m"
40
+ elsif color == :green
41
+ "\e[0;32;49m#{string}\e[0m"
42
+ else
43
+ raise "Unsupported color: #{color}"
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,5 @@
1
+ /* Configure extensions necessary for other queries to work */
2
+
3
+ CREATE EXTENSION IF NOT EXISTS sslinfo;
4
+ CREATE EXTENSION IF NOT EXISTS pg_buffercache;
5
+ CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
@@ -1,4 +1,4 @@
1
- /* Multiple indexes that have the same set of columns, same opclass, expression and predicate -- which make them equivalent. */
1
+ /* Multiple indexes that have the same set of columns, same opclass, expression and predicate. */
2
2
 
3
3
  SELECT pg_size_pretty(sum(pg_relation_size(idx))::bigint) as size,
4
4
  (array_agg(idx))[1] as idx1, (array_agg(idx))[2] as idx2,
@@ -0,0 +1,3 @@
1
+ /* pg_stat_statements_reset discards statistics gathered so far by pg_stat_statements */
2
+
3
+ SELECT pg_stat_statements_reset();
@@ -0,0 +1,3 @@
1
+ /* Check if SSL connection is used */
2
+
3
+ SELECT ssl_is_used();
@@ -11,6 +11,6 @@ SELECT
11
11
  idx_scan as index_scans
12
12
  FROM pg_stat_user_indexes ui
13
13
  JOIN pg_index i ON ui.indexrelid = i.indexrelid
14
- WHERE NOT indisunique AND idx_scan < %{min_scans} AND pg_relation_size(relid) > 5 * 8192
14
+ WHERE NOT indisunique AND idx_scan < %{max_scans} AND pg_relation_size(relid) > 5 * 8192
15
15
  ORDER BY pg_relation_size(i.indexrelid) / nullif(idx_scan, 0) DESC NULLS FIRST,
16
16
  pg_relation_size(i.indexrelid) DESC;
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RubyPGExtras
4
- VERSION = "2.1.0"
4
+ VERSION = "3.1.0"
5
5
  end
@@ -3,20 +3,23 @@
3
3
  require 'terminal-table'
4
4
  require 'uri'
5
5
  require 'pg'
6
+ require 'ruby-pg-extras/diagnose_data'
7
+ require 'ruby-pg-extras/diagnose_print'
6
8
 
7
9
  module RubyPGExtras
8
10
  @@database_url = nil
9
11
  NEW_PG_STAT_STATEMENTS = "1.8"
10
12
 
11
13
  QUERIES = %i(
12
- bloat blocking cache_hit db_settings
14
+ add_extensions bloat blocking cache_hit db_settings
13
15
  calls extensions table_cache_hit index_cache_hit
14
16
  index_size index_usage null_indexes locks all_locks
15
17
  long_running_queries mandelbrot outliers
16
18
  records_rank seq_scans table_indexes_size
17
19
  table_size total_index_size total_table_size
18
20
  unused_indexes duplicate_indexes vacuum_stats kill_all
19
- buffercache_stats buffercache_usage
21
+ pg_stat_statements_reset buffercache_stats
22
+ buffercache_usage ssl_used
20
23
  )
21
24
 
22
25
  DEFAULT_ARGS = Hash.new({}).merge({
@@ -27,7 +30,7 @@ module RubyPGExtras
27
30
  outliers_legacy: { limit: 10 },
28
31
  buffercache_stats: { limit: 10 },
29
32
  buffercache_usage: { limit: 20 },
30
- unused_indexes: { min_scans: 50 },
33
+ unused_indexes: { max_scans: 50 },
31
34
  null_indexes: { min_relation_size_mb: 10 }
32
35
  })
33
36
 
@@ -66,6 +69,20 @@ module RubyPGExtras
66
69
  )
67
70
  end
68
71
 
72
+ def self.diagnose(in_format: :display_table)
73
+ data = RubyPGExtras::DiagnoseData.call
74
+
75
+ if in_format == :display_table
76
+ RubyPGExtras::DiagnosePrint.call(data)
77
+ elsif in_format == :hash
78
+ data
79
+ elsif in_format == :array
80
+ data.map(&:values)
81
+ else
82
+ raise "Invalid 'in_format' argument!"
83
+ end
84
+ end
85
+
69
86
  def self.display_result(result, title:, in_format:)
70
87
  case in_format
71
88
  when :array
Binary file
@@ -16,6 +16,7 @@ Gem::Specification.new do |gem|
16
16
  gem.require_paths = ["lib"]
17
17
  gem.license = "MIT"
18
18
  gem.add_dependency "pg"
19
+ gem.add_dependency "filesize"
19
20
  gem.add_dependency "terminal-table"
20
21
  gem.add_development_dependency "rake"
21
22
  gem.add_development_dependency "rspec"
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe RubyPGExtras::DiagnoseData do
6
+ subject(:result) do
7
+ RubyPGExtras::DiagnoseData.call
8
+ end
9
+
10
+ describe "call" do
11
+ context "stubbed cases" do
12
+ before do
13
+ expect(RubyPGExtras).to receive(:unused_indexes) {
14
+ [
15
+ { "table" => "public.plans", "index" => "index_plans_on_payer_id", "index_size" => "16 MB", "index_scans" => 0 },
16
+ { "table" => "public.feedbacks", "index" => "index_feedbacks_on_target_id", "index_size" => "80 kB", "index_scans" => 1 },
17
+ { "table" => "public.channels", "index" => "index_channels_on_slack_id", "index_size" => "56 MB", "index_scans" => 7}
18
+ ]
19
+ }
20
+
21
+ expect(RubyPGExtras).to receive(:null_indexes) {
22
+ [
23
+ { "oid" => 123, "index" => "index_plans_on_payer_id", "index_size" => "16 MB", "unique" => "t", "null_frac" => "00.00%", "expected_saving" => "0 kb" },
24
+ { "oid" => 321, "index" => "index_feedbacks_on_target_id", "index_size" => "80 kB", "unique" => "f", "null_frac" => "97.00%", "expected_saving" => "77 kb" },
25
+ { "oid" => 231, "index" => "index_channels_on_slack_id", "index_size" => "56 MB", "unique" => "t", "null_frac" => "49.99%", "expected_saving" => "28 MB" }
26
+ ]
27
+ }
28
+
29
+ expect(RubyPGExtras).to receive(:bloat) {
30
+ [
31
+ { "type" => "table", "schemaname" => "public", "object_name" => "bloated_table_1", "bloat" => 8, "waste" => "0 kb" },
32
+ { "type" => "table", "schemaname" => "public", "object_name" => "bloated_table_2", "bloat" => 8, "waste" => "77 kb" },
33
+ { "type" => "schemaname", "public" => "index_channels_on_slack_id", "object_name" => "bloated_index", "bloat" => 11, "waste" => "28 MB" }
34
+ ]
35
+ }
36
+
37
+ expect(RubyPGExtras).to receive(:duplicate_indexes) {
38
+ [
39
+ { "size" => "128 kb", "idx1" => "users_pkey", "idx2" => "index_users_id" }
40
+ ]
41
+ }
42
+
43
+ expect(RubyPGExtras).to receive(:outliers) {
44
+ [
45
+ { "query" => "SELECT * FROM users WHERE users.age > 20 AND users.height > 160", "exec_time" => "154:39:26.431466", "prop_exec_time" => "72.2%", "ncalls" => "34,211,877", "sync_io_time" => "00:34:19.784318" }
46
+ ]
47
+ }
48
+ end
49
+
50
+ it "works" do
51
+ expect {
52
+ RubyPGExtras::DiagnosePrint.call(result)
53
+ }.not_to raise_error
54
+ end
55
+ end
56
+
57
+ context "real database data" do
58
+ before do
59
+ expect(RubyPGExtras).to receive(:unused_indexes) {
60
+ [
61
+ { "table" => "public.plans", "index" => "index_plans_on_payer_id", "index_size" => "2 MB", "index_scans" => 0 },
62
+ { "table" => "public.feedbacks", "index" => "index_feedbacks_on_target_id", "index_size" => "80 kB", "index_scans" => 1 },
63
+ { "table" => "public.channels", "index" => "index_channels_on_slack_id", "index_size" => "1.1 MB", "index_scans" => 7}
64
+ ]
65
+ }
66
+
67
+ end
68
+
69
+ it "works" do
70
+ expect {
71
+ RubyPGExtras::DiagnosePrint.call(result)
72
+ }.not_to raise_error
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe RubyPGExtras::DiagnosePrint do
6
+ subject(:print_result) do
7
+ RubyPGExtras::DiagnosePrint.call(data)
8
+ end
9
+
10
+ let(:data) do
11
+ [
12
+ {
13
+ :check_name => :table_cache_hit,
14
+ :ok => false,
15
+ :message => "Table hit ratio too low: 0.906977."
16
+ },
17
+ {
18
+ :check_name => :index_cache_hit,
19
+ :ok => false,
20
+ :message => "Index hit ratio is too low: 0.818182."
21
+ },
22
+ {
23
+ :check_name => :ssl_used,
24
+ :ok => true,
25
+ :message => "Database client is using a secure SSL connection."
26
+ }
27
+ ]
28
+ end
29
+
30
+ describe "call" do
31
+ it "works" do
32
+ expect {
33
+ print_result
34
+ }.not_to raise_error
35
+ end
36
+ end
37
+ end
data/spec/smoke_spec.rb CHANGED
@@ -3,11 +3,6 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  describe RubyPGExtras do
6
- before(:all) do
7
- RubyPGExtras.connection.exec("CREATE EXTENSION IF NOT EXISTS pg_buffercache;")
8
- RubyPGExtras.connection.exec("CREATE EXTENSION IF NOT EXISTS pg_stat_statements;")
9
- end
10
-
11
6
  RubyPGExtras::QUERIES.each do |query_name|
12
7
  it "#{query_name} description can be read" do
13
8
  expect do
data/spec/spec_helper.rb CHANGED
@@ -17,3 +17,11 @@ else
17
17
  end
18
18
 
19
19
  ENV["DATABASE_URL"] ||= "postgresql://postgres:secret@localhost:#{port}/ruby-pg-extras-test"
20
+
21
+ RSpec.configure do |config|
22
+ config.before(:suite) do
23
+ RubyPGExtras.connection.exec("CREATE EXTENSION IF NOT EXISTS pg_stat_statements;")
24
+ RubyPGExtras.connection.exec("CREATE EXTENSION IF NOT EXISTS pg_buffercache;")
25
+ RubyPGExtras.connection.exec("CREATE EXTENSION IF NOT EXISTS sslinfo;")
26
+ end
27
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-pg-extras
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - pawurb
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-10-15 00:00:00.000000000 Z
11
+ date: 2021-10-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pg
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: filesize
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: terminal-table
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -83,6 +97,9 @@ files:
83
97
  - Rakefile
84
98
  - docker-compose.yml.sample
85
99
  - lib/ruby-pg-extras.rb
100
+ - lib/ruby-pg-extras/diagnose_data.rb
101
+ - lib/ruby-pg-extras/diagnose_print.rb
102
+ - lib/ruby-pg-extras/queries/add_extensions.sql
86
103
  - lib/ruby-pg-extras/queries/all_locks.sql
87
104
  - lib/ruby-pg-extras/queries/bloat.sql
88
105
  - lib/ruby-pg-extras/queries/blocking.sql
@@ -104,8 +121,10 @@ files:
104
121
  - lib/ruby-pg-extras/queries/null_indexes.sql
105
122
  - lib/ruby-pg-extras/queries/outliers.sql
106
123
  - lib/ruby-pg-extras/queries/outliers_legacy.sql
124
+ - lib/ruby-pg-extras/queries/pg_stat_statements_reset.sql
107
125
  - lib/ruby-pg-extras/queries/records_rank.sql
108
126
  - lib/ruby-pg-extras/queries/seq_scans.sql
127
+ - lib/ruby-pg-extras/queries/ssl_used.sql
109
128
  - lib/ruby-pg-extras/queries/table_cache_hit.sql
110
129
  - lib/ruby-pg-extras/queries/table_indexes_size.sql
111
130
  - lib/ruby-pg-extras/queries/table_size.sql
@@ -114,7 +133,10 @@ files:
114
133
  - lib/ruby-pg-extras/queries/unused_indexes.sql
115
134
  - lib/ruby-pg-extras/queries/vacuum_stats.sql
116
135
  - lib/ruby-pg-extras/version.rb
136
+ - ruby-pg-extras-diagnose.png
117
137
  - ruby-pg-extras.gemspec
138
+ - spec/diagnose_data_spec.rb
139
+ - spec/diagnose_print_spec.rb
118
140
  - spec/smoke_spec.rb
119
141
  - spec/spec_helper.rb
120
142
  homepage: http://github.com/pawurb/ruby-pg-extras
@@ -141,5 +163,7 @@ signing_key:
141
163
  specification_version: 4
142
164
  summary: Ruby PostgreSQL performance database insights
143
165
  test_files:
166
+ - spec/diagnose_data_spec.rb
167
+ - spec/diagnose_print_spec.rb
144
168
  - spec/smoke_spec.rb
145
169
  - spec/spec_helper.rb