ruby-pg-extras 2.1.0 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
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