ruby-pg-extras 3.1.0 → 3.2.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: 1b09fa8f29c8e1b0438acf8834d8d51e4ab76b2b8c4c7475e7ac82902d9bc29d
4
- data.tar.gz: 5a18583d3576f515ea05e0a5d74180c11d811a44d5ecca9d5bad001540f28a98
3
+ metadata.gz: 3fd26a79ff47d4681d4e39888c8f1b362ff2c585c526c06b779e0fd84c3bdd7b
4
+ data.tar.gz: c516bc6586e939a6bdf9c815823094a1e1b92ef30c3dcf7064eb9a2b3394a845
5
5
  SHA512:
6
- metadata.gz: 3a24a0a49e083f081d0d023f4f1b05d3fa867ef5b362f99eb6bc3c711616b857c5f434e5c23ffcc2ba42a0890f562f553694cb45434fa5484402569a827fe1fa
7
- data.tar.gz: c50dd6d08c4e470a53257af9a7092271a6abeaab308579dc579efcd7df39e9b42031cc01f3d627225fcc73a26bb5c3253a6fe2c94ccc19812fc9671d88936a9b
6
+ metadata.gz: ecc8790295246456eb2ff3bd7025a08ed0a44b435bcf2af7070b35e2f081eeb302061e976a96d0ef5ef21422168637053fed1931687ec96d44e64f457ec21f51
7
+ data.tar.gz: 4a87d50af4289473bbf40bfe1dfae20bb214feca969ff56b34b3f27ba9321b28c3415443c3374970747877e3d5b3c14c0780e4e3108cdbf757eb1e0426c088d8
@@ -0,0 +1,58 @@
1
+ module RubyPGExtras
2
+ class IndexInfo
3
+ def self.call(table_name = nil)
4
+ new.call(table_name)
5
+ end
6
+
7
+ def call(table_name = nil)
8
+ indexes_data.select do |index_data|
9
+ if table_name == nil
10
+ true
11
+ else
12
+ index_data.fetch("tablename") == table_name
13
+ end
14
+ end.map do |index_data|
15
+ index_name = index_data.fetch("indexname")
16
+ {
17
+ index_name: index_data.fetch("indexname"),
18
+ table_name: index_data.fetch("tablename"),
19
+ columns: index_data.fetch("columns").split(',').map(&:strip),
20
+ index_size: index_size_data.find do |el|
21
+ el.fetch("name") == index_name
22
+ end.fetch("size", "N/A"),
23
+ index_scans: index_scans_data.find do |el|
24
+ el.fetch("index") == index_name
25
+ end.fetch("index_scans", "N/A"),
26
+ null_frac: null_indexes_data.find do |el|
27
+ el.fetch("index") == index_name
28
+ end&.fetch("null_frac", "N/A") || "0.00%"
29
+ }
30
+ end
31
+ end
32
+
33
+ def index_size_data
34
+ @_index_size_data ||= query_module.index_size(in_format: :hash)
35
+ end
36
+
37
+ def null_indexes_data
38
+ @_null_indexes_data ||= query_module.null_indexes(
39
+ in_format: :hash,
40
+ args: { min_relation_size_mb: 0 }
41
+ )
42
+ end
43
+
44
+ def index_scans_data
45
+ @_index_scans_data ||= query_module.index_scans(in_format: :hash)
46
+ end
47
+
48
+ def indexes_data
49
+ @_indexes_data ||= query_module.indexes(in_format: :hash)
50
+ end
51
+
52
+ private
53
+
54
+ def query_module
55
+ RubyPGExtras
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'terminal-table'
4
+
5
+ module RubyPGExtras
6
+ class IndexInfoPrint
7
+ def self.call(data)
8
+ new.call(data)
9
+ end
10
+
11
+ def call(data)
12
+ rows = data.map do |el|
13
+ [
14
+ el.fetch(:index_name),
15
+ el.fetch(:table_name),
16
+ el.fetch(:columns).join(', '),
17
+ el.fetch(:index_size),
18
+ el.fetch(:index_scans),
19
+ el.fetch(:null_frac)
20
+ ]
21
+ end
22
+
23
+ puts Terminal::Table.new(
24
+ headings: [
25
+ "Index name",
26
+ "Table name",
27
+ "Columns",
28
+ "Index size",
29
+ "Index scans",
30
+ "Null frac"
31
+ ],
32
+ title: title,
33
+ rows: rows
34
+ )
35
+ end
36
+
37
+ private
38
+
39
+ def title
40
+ "Index info"
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,12 @@
1
+ /* Number of scans performed on indexes */
2
+
3
+ SELECT
4
+ schemaname,
5
+ relname AS table,
6
+ indexrelname AS index,
7
+ pg_size_pretty(pg_relation_size(i.indexrelid)) AS index_size,
8
+ idx_scan as index_scans
9
+ FROM pg_stat_user_indexes ui
10
+ JOIN pg_index i ON ui.indexrelid = i.indexrelid
11
+ ORDER BY pg_relation_size(i.indexrelid) / nullif(idx_scan, 0) DESC NULLS FIRST,
12
+ pg_relation_size(i.indexrelid) DESC;
@@ -0,0 +1,9 @@
1
+ /* List all the indexes with their corresponding tables and columns. */
2
+
3
+ SELECT
4
+ schemaname,
5
+ indexname,
6
+ tablename,
7
+ rtrim(split_part(indexdef, '(', 2), ')') as columns
8
+ FROM pg_indexes
9
+ where tablename in (select relname from pg_statio_user_tables);
@@ -0,0 +1,7 @@
1
+ /* Count of index scans by table descending by order */
2
+
3
+ SELECT relname AS name,
4
+ idx_scan as count
5
+ FROM
6
+ pg_stat_user_tables
7
+ ORDER BY idx_scan DESC;
@@ -0,0 +1,3 @@
1
+ /* List all the tables. */
2
+
3
+ select relname as tablename, schemaname from pg_statio_user_tables;
@@ -0,0 +1,75 @@
1
+ module RubyPGExtras
2
+ class TableInfo
3
+ def self.call(table_name = nil)
4
+ new.call(table_name)
5
+ end
6
+
7
+ def call(table_name)
8
+ tables_data.select do |table_data|
9
+ if table_name == nil
10
+ true
11
+ else
12
+ table_data.fetch("tablename") == table_name
13
+ end
14
+ end.map do |table_data|
15
+ table_name = table_data.fetch("tablename")
16
+
17
+ {
18
+ table_name: table_name,
19
+ table_size: table_size_data.find do |el|
20
+ el.fetch("name") == table_name
21
+ end.fetch("size", "N/A"),
22
+ table_cache_hit: table_cache_hit_data.find do |el|
23
+ el.fetch("name") == table_name
24
+ end.fetch("ratio", "N/A"),
25
+ indexes_cache_hit: index_cache_hit_data.find do |el|
26
+ el.fetch("name") == table_name
27
+ end.fetch("ratio", "N/A"),
28
+ estimated_rows: records_rank_data.find do |el|
29
+ el.fetch("name") == table_name
30
+ end.fetch("estimated_count", "N/A"),
31
+ sequential_scans: seq_scans_data.find do |el|
32
+ el.fetch("name") == table_name
33
+ end.fetch("count", "N/A"),
34
+ indexes_scans: table_index_scans_data.find do |el|
35
+ el.fetch("name") == table_name
36
+ end.fetch("count", "N/A")
37
+ }
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def index_cache_hit_data
44
+ @_index_cache_hit_data ||= query_module.index_cache_hit(in_format: :hash)
45
+ end
46
+
47
+ def table_cache_hit_data
48
+ @_table_cache_hit_data ||= query_module.table_cache_hit(in_format: :hash)
49
+ end
50
+
51
+ def table_size_data
52
+ @_table_size_data ||= query_module.table_size(in_format: :hash)
53
+ end
54
+
55
+ def records_rank_data
56
+ @_records_rank_data ||= query_module.records_rank(in_format: :hash)
57
+ end
58
+
59
+ def tables_data
60
+ @_tables_data ||= query_module.tables(in_format: :hash)
61
+ end
62
+
63
+ def seq_scans_data
64
+ @_seq_scans_data ||= query_module.seq_scans(in_format: :hash)
65
+ end
66
+
67
+ def table_index_scans_data
68
+ @_table_index_scans_data ||= query_module.table_index_scans(in_format: :hash)
69
+ end
70
+
71
+ def query_module
72
+ RubyPGExtras
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'terminal-table'
4
+
5
+ module RubyPGExtras
6
+ class TableInfoPrint
7
+ def self.call(data)
8
+ new.call(data)
9
+ end
10
+
11
+ def call(data)
12
+ rows = data.map do |el|
13
+ [
14
+ el.fetch(:table_name),
15
+ el.fetch(:table_size),
16
+ el.fetch(:table_cache_hit),
17
+ el.fetch(:indexes_cache_hit),
18
+ el.fetch(:estimated_rows),
19
+ el.fetch(:sequential_scans),
20
+ el.fetch(:indexes_scans)
21
+ ]
22
+ end
23
+
24
+ puts Terminal::Table.new(
25
+ headings: [
26
+ "Table name",
27
+ "Table size",
28
+ "Table cache hit",
29
+ "Indexes cache hit",
30
+ "Estimated rows",
31
+ "Sequentail scans",
32
+ "Indexes scans"
33
+ ],
34
+ title: title,
35
+ rows: rows
36
+ )
37
+ end
38
+
39
+ private
40
+
41
+ def title
42
+ "Table info"
43
+ end
44
+ end
45
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RubyPGExtras
4
- VERSION = "3.1.0"
4
+ VERSION = "3.2.0"
5
5
  end
@@ -5,6 +5,10 @@ require 'uri'
5
5
  require 'pg'
6
6
  require 'ruby-pg-extras/diagnose_data'
7
7
  require 'ruby-pg-extras/diagnose_print'
8
+ require 'ruby-pg-extras/index_info'
9
+ require 'ruby-pg-extras/index_info_print'
10
+ require 'ruby-pg-extras/table_info'
11
+ require 'ruby-pg-extras/table_info_print'
8
12
 
9
13
  module RubyPGExtras
10
14
  @@database_url = nil
@@ -12,10 +16,10 @@ module RubyPGExtras
12
16
 
13
17
  QUERIES = %i(
14
18
  add_extensions bloat blocking cache_hit db_settings
15
- calls extensions table_cache_hit index_cache_hit
16
- index_size index_usage null_indexes locks all_locks
19
+ calls extensions table_cache_hit tables index_cache_hit
20
+ indexes index_size index_usage index_scans null_indexes locks all_locks
17
21
  long_running_queries mandelbrot outliers
18
- records_rank seq_scans table_indexes_size
22
+ records_rank seq_scans table_index_scans table_indexes_size
19
23
  table_size total_index_size total_table_size
20
24
  unused_indexes duplicate_indexes vacuum_stats kill_all
21
25
  pg_stat_statements_reset buffercache_stats
@@ -83,6 +87,34 @@ module RubyPGExtras
83
87
  end
84
88
  end
85
89
 
90
+ def self.index_info(args:, in_format: :display_table)
91
+ data = RubyPGExtras::IndexInfo.call(args[:table_name])
92
+
93
+ if in_format == :display_table
94
+ RubyPGExtras::IndexInfoPrint.call(data)
95
+ elsif in_format == :hash
96
+ data
97
+ elsif in_format == :array
98
+ data.map(&:values)
99
+ else
100
+ raise "Invalid 'in_format' argument!"
101
+ end
102
+ end
103
+
104
+ def self.table_info(args:, in_format: :display_table)
105
+ data = RubyPGExtras::TableInfo.call(args[:table_name])
106
+
107
+ if in_format == :display_table
108
+ RubyPGExtras::TableInfoPrint.call(data)
109
+ elsif in_format == :hash
110
+ data
111
+ elsif in_format == :array
112
+ data.map(&:values)
113
+ else
114
+ raise "Invalid 'in_format' argument!"
115
+ end
116
+ end
117
+
86
118
  def self.display_result(result, title:, in_format:)
87
119
  case in_format
88
120
  when :array
@@ -20,9 +20,9 @@ describe RubyPGExtras::DiagnoseData do
20
20
 
21
21
  expect(RubyPGExtras).to receive(:null_indexes) {
22
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" }
23
+ { "oid" => 123, "index" => "index_plans_on_payer_id", "index_size" => "16 MB", "unique" => true, "null_frac" => "00.00%", "expected_saving" => "0 kb" },
24
+ { "oid" => 321, "index" => "index_feedbacks_on_target_id", "index_size" => "80 kB", "unique" => true, "null_frac" => "97.00%", "expected_saving" => "77 kb" },
25
+ { "oid" => 231, "index" => "index_channels_on_slack_id", "index_size" => "56 MB", "unique" => true, "null_frac" => "49.99%", "expected_saving" => "28 MB" }
26
26
  ]
27
27
  }
28
28
 
@@ -55,17 +55,6 @@ describe RubyPGExtras::DiagnoseData do
55
55
  end
56
56
 
57
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
58
  it "works" do
70
59
  expect {
71
60
  RubyPGExtras::DiagnosePrint.call(result)
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe RubyPGExtras::IndexInfo do
6
+ subject(:result) do
7
+ RubyPGExtras::IndexInfo.call
8
+ end
9
+
10
+ describe "call" do
11
+ context "stubbed cases" do
12
+ before do
13
+ expect(RubyPGExtras).to receive(:indexes) {
14
+ [
15
+ { "schemaname" => "public", "indexname" => "index_users_on_api_auth_token", "tablename" => "users", "columns" => "api_auth_token, column2" },
16
+ {"schemaname" => "public", "indexname" => "index_teams_on_slack_id", "tablename" => "teams", "columns" => "slack_id" },
17
+ ]
18
+ }
19
+
20
+ expect(RubyPGExtras).to receive(:index_size) {
21
+ [
22
+ { "name" => "index_users_on_api_auth_token", "size" => "1744 kB" },
23
+ {"name" => "index_teams_on_slack_id", "size" => "500 kB"},
24
+ ]
25
+ }
26
+
27
+ expect(RubyPGExtras).to receive(:null_indexes) {
28
+ [
29
+ { "oid" => 16803, "index" => "index_users_on_api_auth_token", "index_size" => "1744 kB", "unique"=>true, "indexed_column" => "api_auth_token", "null_frac" => "25.00%", "expected_saving" => "300 kB" }
30
+ ]
31
+ }
32
+
33
+ expect(RubyPGExtras).to receive(:index_scans) {
34
+ [
35
+ { "schemaname" => "public", "table" => "users", "index" => "index_users_on_api_auth_token", "index_size" => "1744 kB", "index_scans"=> 0 },
36
+ { "schemaname" => "public", "table" => "teams", "index" => "index_teams_on_slack_id", "index_size" => "500 kB", "index_scans"=> 0 }
37
+ ]
38
+ }
39
+ end
40
+
41
+ it "works" do
42
+ expect {
43
+ RubyPGExtras::IndexInfoPrint.call(result)
44
+ }.not_to raise_error
45
+ end
46
+ end
47
+
48
+ context "real data" do
49
+ it "works" do
50
+ expect {
51
+ RubyPGExtras::IndexInfoPrint.call(result)
52
+ }.not_to raise_error
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe RubyPGExtras::TableInfo do
6
+ subject(:result) do
7
+ RubyPGExtras::TableInfo.call
8
+ end
9
+
10
+ describe "call" do
11
+ context "stubbed cases" do
12
+ before do
13
+ expect(RubyPGExtras).to receive(:tables) {
14
+ [
15
+ { "schemaname" => "public", "tablename" => "users" },
16
+ { "schemaname" => "public", "tablename" => "teams" }
17
+ ]
18
+ }
19
+
20
+ expect(RubyPGExtras).to receive(:table_size) {
21
+ [
22
+ { "name" => "teams", "size" => "25 MB" },
23
+ {"name" => "users", "size" => "250 MB"},
24
+ ]
25
+ }
26
+
27
+ expect(RubyPGExtras).to receive(:index_cache_hit) {
28
+ [
29
+ { "name" => "teams", "ratio" => "0.98" },
30
+ { "name" => "users", "ratio" => "0.999" },
31
+ ]
32
+ }
33
+
34
+ expect(RubyPGExtras).to receive(:table_cache_hit) {
35
+ [
36
+ { "name" => "teams", "ratio" => "0.88" },
37
+ { "name" => "users", "ratio" => "0.899" },
38
+ ]
39
+ }
40
+
41
+ expect(RubyPGExtras).to receive(:records_rank) {
42
+ [
43
+ { "name" => "teams", "estimated_count" => "358" },
44
+ { "name" => "users", "estimated_count" => "8973" },
45
+ ]
46
+ }
47
+
48
+ expect(RubyPGExtras).to receive(:seq_scans) {
49
+ [
50
+ { "name" => "teams", "count" => "0" },
51
+ { "name" => "users", "count" => "409328" },
52
+ ]
53
+ }
54
+
55
+ expect(RubyPGExtras).to receive(:table_index_scans) {
56
+ [
57
+ { "name" => "teams", "count" => "8579" },
58
+ { "name" => "users", "count" => "0" },
59
+ ]
60
+ }
61
+ end
62
+
63
+ it "works" do
64
+ expect {
65
+ RubyPGExtras::TableInfoPrint.call(result)
66
+ }.not_to raise_error
67
+ end
68
+ end
69
+
70
+ context "real data" do
71
+ it "works" do
72
+ expect {
73
+ RubyPGExtras::TableInfoPrint.call(result)
74
+ }.not_to raise_error
75
+ end
76
+ end
77
+ end
78
+ 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: 3.1.0
4
+ version: 3.2.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-29 00:00:00.000000000 Z
11
+ date: 2021-12-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pg
@@ -99,6 +99,8 @@ files:
99
99
  - lib/ruby-pg-extras.rb
100
100
  - lib/ruby-pg-extras/diagnose_data.rb
101
101
  - lib/ruby-pg-extras/diagnose_print.rb
102
+ - lib/ruby-pg-extras/index_info.rb
103
+ - lib/ruby-pg-extras/index_info_print.rb
102
104
  - lib/ruby-pg-extras/queries/add_extensions.sql
103
105
  - lib/ruby-pg-extras/queries/all_locks.sql
104
106
  - lib/ruby-pg-extras/queries/bloat.sql
@@ -112,8 +114,10 @@ files:
112
114
  - lib/ruby-pg-extras/queries/duplicate_indexes.sql
113
115
  - lib/ruby-pg-extras/queries/extensions.sql
114
116
  - lib/ruby-pg-extras/queries/index_cache_hit.sql
117
+ - lib/ruby-pg-extras/queries/index_scans.sql
115
118
  - lib/ruby-pg-extras/queries/index_size.sql
116
119
  - lib/ruby-pg-extras/queries/index_usage.sql
120
+ - lib/ruby-pg-extras/queries/indexes.sql
117
121
  - lib/ruby-pg-extras/queries/kill_all.sql
118
122
  - lib/ruby-pg-extras/queries/locks.sql
119
123
  - lib/ruby-pg-extras/queries/long_running_queries.sql
@@ -126,19 +130,25 @@ files:
126
130
  - lib/ruby-pg-extras/queries/seq_scans.sql
127
131
  - lib/ruby-pg-extras/queries/ssl_used.sql
128
132
  - lib/ruby-pg-extras/queries/table_cache_hit.sql
133
+ - lib/ruby-pg-extras/queries/table_index_scans.sql
129
134
  - lib/ruby-pg-extras/queries/table_indexes_size.sql
130
135
  - lib/ruby-pg-extras/queries/table_size.sql
136
+ - lib/ruby-pg-extras/queries/tables.sql
131
137
  - lib/ruby-pg-extras/queries/total_index_size.sql
132
138
  - lib/ruby-pg-extras/queries/total_table_size.sql
133
139
  - lib/ruby-pg-extras/queries/unused_indexes.sql
134
140
  - lib/ruby-pg-extras/queries/vacuum_stats.sql
141
+ - lib/ruby-pg-extras/table_info.rb
142
+ - lib/ruby-pg-extras/table_info_print.rb
135
143
  - lib/ruby-pg-extras/version.rb
136
144
  - ruby-pg-extras-diagnose.png
137
145
  - ruby-pg-extras.gemspec
138
146
  - spec/diagnose_data_spec.rb
139
147
  - spec/diagnose_print_spec.rb
148
+ - spec/index_info_spec.rb
140
149
  - spec/smoke_spec.rb
141
150
  - spec/spec_helper.rb
151
+ - spec/table_info_spec.rb
142
152
  homepage: http://github.com/pawurb/ruby-pg-extras
143
153
  licenses:
144
154
  - MIT
@@ -165,5 +175,7 @@ summary: Ruby PostgreSQL performance database insights
165
175
  test_files:
166
176
  - spec/diagnose_data_spec.rb
167
177
  - spec/diagnose_print_spec.rb
178
+ - spec/index_info_spec.rb
168
179
  - spec/smoke_spec.rb
169
180
  - spec/spec_helper.rb
181
+ - spec/table_info_spec.rb