pgdexter 0.3.1 → 0.3.2
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 +4 -4
- data/CHANGELOG.md +6 -0
- data/exe/dexter +2 -0
- data/lib/dexter/client.rb +6 -7
- data/lib/dexter/csv_log_parser.rb +7 -2
- data/lib/dexter/indexer.rb +31 -24
- data/lib/dexter/log_parser.rb +3 -1
- data/lib/dexter/logging.rb +4 -0
- data/lib/dexter/query.rb +1 -1
- data/lib/dexter/version.rb +1 -1
- data/lib/dexter.rb +4 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 714767248afe28ad9e354ebb6485b34a8d6aadd0
|
4
|
+
data.tar.gz: 545eaaea1df0312049f4cecff3fe9b03b80a6cd9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '096684887c5d5a48a2df74a2dae6cf70bc2b5e98dd5dc6d7d6d8b583bfb76193ce86166972d9553f89452f16629a8576a30f2627fd8d0b16a9672ce76a316835'
|
7
|
+
data.tar.gz: d128734845414672a3f470f138c6a422dc2b24024a66866e50524fe3bb3e949bd4a899305a93f71fb14af3b56820f66e20a00d5ba5788cd0c88bd0afaa064e32
|
data/CHANGELOG.md
CHANGED
data/exe/dexter
CHANGED
data/lib/dexter/client.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
module Dexter
|
2
2
|
class Client
|
3
|
+
include Logging
|
4
|
+
|
3
5
|
attr_reader :arguments, :options
|
4
6
|
|
5
7
|
def initialize(args)
|
@@ -14,6 +16,7 @@ module Dexter
|
|
14
16
|
query = Query.new(options[:statement])
|
15
17
|
Indexer.new(options).process_queries([query])
|
16
18
|
elsif options[:pg_stat_statements]
|
19
|
+
# TODO support streaming option
|
17
20
|
Indexer.new(options).process_stat_statements
|
18
21
|
elsif arguments.any?
|
19
22
|
ARGV.replace(arguments)
|
@@ -35,12 +38,12 @@ Options:)
|
|
35
38
|
o.string "--include", "only include specific tables"
|
36
39
|
o.string "--input-format", "input format", default: "stderr"
|
37
40
|
o.integer "--interval", "time to wait between processing queries, in seconds", default: 60
|
38
|
-
o.float "--min-calls", "only process queries that have been called a certain number of times", default: 0
|
39
|
-
o.float "--min-time", "only process queries that have consumed a certain amount of DB time, in minutes", default: 0
|
40
|
-
o.boolean "--pg-stat-statements", "use pg_stat_statements", default: false, help: false
|
41
41
|
o.boolean "--log-explain", "log explain", default: false, help: false
|
42
42
|
o.string "--log-level", "log level", default: "info"
|
43
43
|
o.boolean "--log-sql", "log sql", default: false
|
44
|
+
o.float "--min-calls", "only process queries that have been called a certain number of times", default: 0
|
45
|
+
o.float "--min-time", "only process queries that have consumed a certain amount of DB time, in minutes", default: 0
|
46
|
+
o.boolean "--pg-stat-statements", "use pg_stat_statements", default: false, help: false
|
44
47
|
o.string "-s", "--statement", "process a single statement"
|
45
48
|
# separator must go here to show up correctly - slop bug?
|
46
49
|
o.separator ""
|
@@ -72,9 +75,5 @@ Options:)
|
|
72
75
|
rescue Slop::Error => e
|
73
76
|
abort e.message
|
74
77
|
end
|
75
|
-
|
76
|
-
def log(message)
|
77
|
-
$stderr.puts message
|
78
|
-
end
|
79
78
|
end
|
80
79
|
end
|
@@ -2,10 +2,15 @@ require "csv"
|
|
2
2
|
|
3
3
|
module Dexter
|
4
4
|
class CsvLogParser < LogParser
|
5
|
+
FIRST_LINE_REGEX = /\A.+/
|
6
|
+
|
5
7
|
def perform
|
6
|
-
CSV.new(@logfile).each do |row|
|
8
|
+
CSV.new(@logfile.to_io).each do |row|
|
7
9
|
if (m = REGEX.match(row[13]))
|
8
|
-
|
10
|
+
# replace first line with match
|
11
|
+
# needed for multiline queries
|
12
|
+
active_line = row[13].sub(FIRST_LINE_REGEX, m[3])
|
13
|
+
|
9
14
|
add_parameters(active_line, row[14]) if row[14]
|
10
15
|
process_entry(active_line, m[1].to_f)
|
11
16
|
end
|
data/lib/dexter/indexer.rb
CHANGED
@@ -30,21 +30,11 @@ module Dexter
|
|
30
30
|
|
31
31
|
tables = Set.new(database_tables)
|
32
32
|
|
33
|
-
if @include_tables
|
34
|
-
include_set = Set.new(@include_tables)
|
35
|
-
tables.keep_if { |t| include_set.include?(t) || include_set.include?(t.split(".")[-1]) }
|
36
|
-
end
|
37
|
-
|
38
|
-
if @exclude_tables.any?
|
39
|
-
exclude_set = Set.new(@exclude_tables)
|
40
|
-
tables.delete_if { |t| exclude_set.include?(t) || exclude_set.include?(t.split(".")[-1]) }
|
41
|
-
end
|
42
|
-
|
43
33
|
# map tables without schema to schema
|
44
34
|
no_schema_tables = {}
|
45
35
|
search_path_index = Hash[search_path.map.with_index.to_a]
|
46
36
|
tables.group_by { |t| t.split(".")[-1] }.each do |group, t2|
|
47
|
-
no_schema_tables[group] = t2.sort_by { |t| search_path_index[t.split(".")[0]] || 1000000 }[0]
|
37
|
+
no_schema_tables[group] = t2.sort_by { |t| [search_path_index[t.split(".")[0]] || 1000000, t] }[0]
|
48
38
|
end
|
49
39
|
|
50
40
|
# filter queries from other databases and system tables
|
@@ -59,11 +49,29 @@ module Dexter
|
|
59
49
|
# set tables
|
60
50
|
tables = Set.new(queries.reject(&:missing_tables).flat_map(&:tables))
|
61
51
|
|
52
|
+
# must come after missing tables set
|
53
|
+
if @include_tables
|
54
|
+
include_set = Set.new(@include_tables)
|
55
|
+
tables.keep_if { |t| include_set.include?(t) || include_set.include?(t.split(".")[-1]) }
|
56
|
+
end
|
57
|
+
|
58
|
+
if @exclude_tables.any?
|
59
|
+
exclude_set = Set.new(@exclude_tables)
|
60
|
+
tables.delete_if { |t| exclude_set.include?(t) || exclude_set.include?(t.split(".")[-1]) }
|
61
|
+
end
|
62
|
+
|
63
|
+
# remove system tables
|
64
|
+
tables.delete_if { |t| t.start_with?("information_schema.") || t.start_with?("pg_catalog.") }
|
65
|
+
|
66
|
+
queries.each do |query|
|
67
|
+
query.candidate_tables = !query.missing_tables && query.tables.any? { |t| tables.include?(t) }
|
68
|
+
end
|
69
|
+
|
62
70
|
# analyze tables if needed
|
63
71
|
analyze_tables(tables) if tables.any? && (@analyze || @log_level == "debug2")
|
64
72
|
|
65
73
|
# create hypothetical indexes and explain queries
|
66
|
-
candidates = tables.any? ? create_hypothetical_indexes(queries.
|
74
|
+
candidates = tables.any? ? create_hypothetical_indexes(queries.select(&:candidate_tables), tables) : {}
|
67
75
|
|
68
76
|
# see if new indexes were used and meet bar
|
69
77
|
new_indexes = determine_indexes(queries, candidates, tables)
|
@@ -344,7 +352,14 @@ module Dexter
|
|
344
352
|
log "-" * 80
|
345
353
|
log "Query #{query.fingerprint}"
|
346
354
|
log "Total time: #{(query.total_time / 60000.0).round(1)} min, avg time: #{(query.total_time / query.calls.to_f).round} ms, calls: #{query.calls}" if query.total_time
|
347
|
-
|
355
|
+
|
356
|
+
if query.fingerprint == "unknown"
|
357
|
+
log "Could not parse query"
|
358
|
+
elsif query.tables.empty?
|
359
|
+
log "No tables"
|
360
|
+
elsif query.missing_tables
|
361
|
+
log "Tables not present in current database"
|
362
|
+
elsif !query.candidate_tables
|
348
363
|
log "No candidate tables for indexes"
|
349
364
|
elsif query.explainable? && !query.high_cost?
|
350
365
|
log "Low initial cost: #{query.initial_cost}"
|
@@ -354,15 +369,9 @@ module Dexter
|
|
354
369
|
log "Pass1: #{query.costs[1]} : #{log_indexes(query.pass1_indexes || [])}"
|
355
370
|
log "Pass2: #{query.costs[2]} : #{log_indexes(query.pass2_indexes || [])}"
|
356
371
|
log "Final: #{query.new_cost} : #{log_indexes(query.suggest_index ? query_indexes : [])}"
|
357
|
-
if query_indexes.
|
372
|
+
if query_indexes.size == 1 && !query.suggest_index
|
358
373
|
log "Need 50% cost savings to suggest index"
|
359
374
|
end
|
360
|
-
elsif query.fingerprint == "unknown"
|
361
|
-
log "Could not parse query"
|
362
|
-
elsif query.tables.empty?
|
363
|
-
log "No tables"
|
364
|
-
elsif query.missing_tables
|
365
|
-
log "Tables not present in current database"
|
366
375
|
else
|
367
376
|
log "Could not run explain"
|
368
377
|
end
|
@@ -452,9 +461,7 @@ module Dexter
|
|
452
461
|
FROM
|
453
462
|
information_schema.tables
|
454
463
|
WHERE
|
455
|
-
table_catalog = current_database()
|
456
|
-
table_schema NOT IN ('pg_catalog', 'information_schema')
|
457
|
-
AND table_type = 'BASE TABLE'
|
464
|
+
table_catalog = current_database()
|
458
465
|
SQL
|
459
466
|
result.map { |r| r["table_name"] }
|
460
467
|
end
|
@@ -549,7 +556,7 @@ module Dexter
|
|
549
556
|
end
|
550
557
|
|
551
558
|
def search_path
|
552
|
-
execute("
|
559
|
+
execute("SELECT current_schemas(true)")[0]["current_schemas"][1..-2].split(",")
|
553
560
|
end
|
554
561
|
|
555
562
|
def unquote(part)
|
data/lib/dexter/log_parser.rb
CHANGED
data/lib/dexter/logging.rb
CHANGED
data/lib/dexter/query.rb
CHANGED
@@ -2,7 +2,7 @@ module Dexter
|
|
2
2
|
class Query
|
3
3
|
attr_reader :statement, :fingerprint, :plans
|
4
4
|
attr_writer :tables
|
5
|
-
attr_accessor :missing_tables, :new_cost, :total_time, :calls, :indexes, :suggest_index, :pass1_indexes, :pass2_indexes
|
5
|
+
attr_accessor :missing_tables, :new_cost, :total_time, :calls, :indexes, :suggest_index, :pass1_indexes, :pass2_indexes, :candidate_tables
|
6
6
|
|
7
7
|
def initialize(statement, fingerprint = nil)
|
8
8
|
@statement = statement
|
data/lib/dexter/version.rb
CHANGED
data/lib/dexter.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pgdexter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-01-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: slop
|